Saving a file on server

Developer
Aug 11, 2010 at 3:41 PM
Edited Aug 11, 2010 at 4:00 PM
Hello,
I'm trying to save a file on server with a mix of mdo:callable and a server-side script, but cannot get a successfull result. Is this a wrong approach?

I have a SaveFile server side function (vb), which should be called from an mdo:callable js function, which is called by a js function launched when user clicks an input buton:

<msxsl:script language="VB" implements-prefix="user">
        public Function SaveFile(ByVal Text As String) As String
        Dim w As IO.StreamWriter
        w = IO.File.AppendText("c:\temp\log.txt")
        w.Write(Text)
        w.Flush()
        w.Close()
        return "file saved"
        End Function
    </msxsl:script>
<script type="text/javascript">
    <![CDATA[
      function SalvaFile()
    {
    var html = document.body.innerHTML;
    salva(html);
    alert('done');
    }
    ]]>
</script>
<mdo:callable js="salva(content)" type="text/html">
    <xsl:value-of select="user:SaveFile(content)"/>
</mdo:callable>

<input type="button" value="Stampa" onclick="SalvaFile();"></input>


I can get the file saved if I include the call to server-side function in the normal template, but get an error that prefix 'user' is not defined when calling it from mdo:callable. Is there a way to call a server-side function from mdo:callable?
Developer
Aug 11, 2010 at 4:24 PM
Edited Aug 11, 2010 at 4:25 PM
Ah, I found a different (simpler) solution: once again just have to use ajax to submit a form, after filling-in my content input via jQuery at document.ready function.

 <input type="hidden" name="content" id="content"></input>
      a href="{mdo:jsubmit('@action', 'stampa')}">STAMPA
          <xsl:value-of select="user:SaveFile(mdo:request('content'))"/>
      </xsl:if>




Are there other ways to save a file to filesystem?
Coordinator
Aug 11, 2010 at 6:48 PM

Hi,

There are some misunderstandings about how XsltDb adds tags to complete code to correct XSLT.

First, here is working sample. Below I explain your mistakes.

<mdo:callable js="salva(content)" type="text/html">

<msxsl:script language="VB" implements-prefix="script">
        public Function SaveFile(ByVal Text As String) As String
        Dim w As IO.StreamWriter
        w = IO.File.AppendText("c:\temp\log.txt")
        w.Write(Text)
        w.Close()
        return "file saved"
        End Function
    </msxsl:script>
<xsl:template match="/">
    <xsl:value-of select="script:SaveFile(mdo:request('content'))"/>
</xsl:template>
</mdo:callable>

<script type="text/javascript">
    <![CDATA[
      function SalvaFile()
    {
    var html = document.body.innerHTML;
    salva(html);
    alert('done');
    }
    ]]>
</script>

<input type="button" value="Stampa" onclick="SalvaFile();"></input>

 

1. mdo:callable is a separate full XSLT.

<mdo:callable is processed as separate XSL transformation. This means that code inside <mdo:callable> ... </mdo:callable> must be a FULL XSLT (but stil can omit some tags as xsl:stylesheet and xsl:template).  So you have to put msxsl:script inside mdo:callable. This is because when you call a generated javascript function XsltDb must execute ONLY what you write inside mdo:callable.

2. prefix for msxsl:script functions or why user:SaveFile() fails.

If you are using msxsl:script you must declare prefix in xsl:stylesheet tag for your functions. If you omit xsl:stylesheet XsltDb defines prefix "script". So if you want to use user:SaveFile() you have to add xsl:stylesheet tag inside mdo:callable and define prefix "user".

3. Getting values of AJAX function parameters on server.

Parameters are sent using POST so you must use mdo:request() to obtain a value. Instead of

        <xsl:value-of select="user:SaveFile(content)"/>

you should use

        <xsl:value-of select="user:SaveFile(mdo:request('content'))"/>

4. Asyncronous call.

This is not good idea to make syncronous call to javascript function, However it works fine if network connection is stable and web server is fast.
But I recommend instead of

      salva(html);
      alert('done');

this asynchronous form:

      salva(html, function(result){
         alert('done');
      });

Asyncronouse calls don't block browser and you can show gif progress indicator. When you use syncronouse method bowser is blocked and gif / flash objects are frozen.

 

Thank you very much for your questins!
I'm going to start with manual in september and you give me a good understanding of what should be explained in more details.

 

Coordinator
Aug 11, 2010 at 6:59 PM

... And XsltDb provides it's own way to save uploaded files to disk in Year \ Month \ Day folder structure. This allows you to store thousand files daily without having a problem with millions of files in one single folder. See mdo:request-file() for details. And also if you have a grafical file on server you can generate and automatically cache preview for it with mdo:thumbnail()

This works fine with terabytes of files on my "web page filpper" service: http://pressa-online.com/

Developer
Aug 12, 2010 at 12:32 PM
Anton,

thanks once again for your detailed explanation: you always provide me a cleaner solution, great! :)
I actually think this is one of the best DNN modules I ever used... with so many features it's hard to keep documentation up-to-date!

I'm now trying a new thing: launching a process from within an xslt. Calling Process.Start in a server-side function leads to a security exception (I have a trace if you want to see why): I doubt it will be hard to bypass such a problem, but I think I'll try to use mdo:net to call Process.Start from within a separate dll. Did you ever try something similar?

Coordinator
Aug 12, 2010 at 1:12 PM

I didn't try executing processes but dealing with unmanaged stuff requires

    <trust level="Full" />

in web.config, check your trust level, please. Or you can create a permission only for what you need. details here: http://msdn.microsoft.com/en-us/library/wyts434y.aspx

Developer
Aug 12, 2010 at 2:43 PM
Anton,
I then solved by compiling a simple DLL in Mdo.XsltDb namespace, which I call via mdo:net: it works perfectly, no need to touch web.config.

BTW: with this I'm developing an xslt that allows users to request a PDF of the current page, launching wkhtmltopdf.exe (not yet available as library, but that's the best free pdf converter utility I found).
Developer
Aug 27, 2010 at 8:12 AM

Hi,

I'm trying to use an mdo:files call to show files in a directory, but cannot get a result. I also see somewhere thre's a portal-files method, but did not get the difference.

Should I pass a complete or relative path to this function? Phisical or virtual path? For example, if I want to list files in my \portals\0\Img folder, should I do:

 

<xsl:for-each select="mdo:files('/Img')//file">
          File: {{.}} {{dir}} {{file}}<br/>
 </xsl:for-each>

 

or

 

<xsl:for-each select="mdo:files('/Portals/0/Img')//file">
          File: {{.}} {{dir}} {{file}}<br/>
 </xsl:for-each>

 

or maybe

<xsl:for-each select="mdo:files('c:\inetpub\wwwroot\dnn\Portals\0\Img')//file">
          File: {{.}} {{dir}} {{file}}<br/>
 </xsl:for-each>

?

Coordinator
Aug 27, 2010 at 10:18 AM

Hello,

You should use mdo:portal-files to read files from portal's home directory, Unfortunately, it is not still documented. But you have sources :)

<xsl:for-each select="mdo:portal-files('Img')//file">
          File: {{.}} <br/>
</xsl:for-each>

mdo:files returns files from special folder (XsltDbFiles) that is used to store files by mdo:request-file extension.

Developer
Aug 27, 2010 at 11:07 AM
Edited Aug 27, 2010 at 11:08 AM

Anton,

you're right, I have to better look at sources! 

So with mdo:requestfile we can easy save files in XsltDb "repository" (its folder and db table): it would be nice to have a method to save files in DNN Files table, in any folder under portal root - or am I missing an existing method?

Also, why don't you save the original filename in that table, along with the generated guid?

 

Well, other ideas...

I also would like to have ISearchable implemented, so that content is available to DNN searchengine :-)

And what about handling different content types for mdo:callable? It can now give back text/html, text/xml or text/plain isn't it? Would it be possible to handle something like "application/octet-stream", so to send a stream  (for example a file, maybe via a mdo:net call) to client?

 

 

Coordinator
Aug 27, 2010 at 12:25 PM

1. Place of XsltDbFiles folder (that is XsltDb files repository root) can be defined in web.config appSettings as follows:

    <add key="XsltDbFiles" value="AllFiles\{0}\XsltDbFiles" />

This is not documented yet... If this path is relative it is added to current web application folder. {0} tag means portal ID.

2. About file name. I plan to switch from guid.ext form to guid\filename.ext But it is not high priority task for me and, besides, I have more that 1 TB files to rename on 24/7 service. 

3. ISearchable. This work is in progress now. Can't say exactly when I release it but in 1-2-3 weeks maybe. ISearchable will be mapped to <mdo:searchable/> section.

4. mdo:callable and also mdo:service. At the moment you can use type attribute to specify any type of textual content as far as XSL produces textual output. That features was designed to provide datasource for AJAX controls, return HTML fragments, etc. For example, you can easily read and return RTF file as it is also textual file. I must thik how to generate binary content. Do you have any real tasks where such feature can be used? It could help me with design.

Developer
Aug 27, 2010 at 1:28 PM

Anton,

1. Great! You should rent a secretary to write documentation for you :-D

2. I agree that's not prioritary, one could always use a custom table.

3. Good news :-D

4. I have a prototype of an XSL that allows you to get a page printed to PDF: nothing wonderful, but with some evolution it could be useful.

  • I pass current page html to a mdo:net function that saves the html on server (so the printed page is exactly what user sees);
  • I then call another mdo:net method to convert the file to PDF; such function uses wkhtml2pdf, launching the exe that will save a pdf to a file; the function returns the virtual path to the file (which must be saved within my Portal root);
  • I then redirect the user to the pdf, by its URL.

The problem with this is that I have no control on temporary files: when they are created, they remain on server until I manually delete them. I could use a scheduler to clean the folder, but would better NOT save temporary files: if I could stream the file back to the client, I could also delete the tmp file when done.

Streaming could also be useful for some data-conversion function: say you have some data (xml, db table, whatever) you wish to convert to an Excel file. A poterntial "mdo:convert-to-xls" method could also return a virtual path of the generated file to the user, but again it would be nice if it could directly stream the file, without saving it on disk.

Do you think this would be useful?

 

Coordinator
Aug 27, 2010 at 1:45 PM

Yes, this is common consideration, I'll think about how to integrate "binary feature" into XsltDb.

What can be easily implemented here is say "Universal Temporary File Getter". You just create a simple aspx or ashx file that reads file, deleted it from disk and send it to browser.

Coordinator
Sep 10, 2010 at 9:50 AM
trapias wrote:

Anton,

I also would like to have ISearchable implemented, so that content is available to DNN searchengine :-)

Alberto,

Good news.

ISearchable is implemented in XsltDb 02.00.06. This fearure has poor documentation for the moment but if you understand how ISearchable works you can easily make your XsltDb modules searchable.

Developer
Sep 10, 2010 at 10:39 AM

You are the man! :-D

Will try it asap, thank you.

 

Coordinator
Dec 12, 2010 at 10:22 AM
trapias wrote:

The problem with this is that I have no control on temporary files: when they are created, they remain on server until I manually delete them. I could use a scheduler to clean the folder, but would better NOT save temporary files: if I could stream the file back to the client, I could also delete the tmp file when done.

Streaming could also be useful for some data-conversion function: say you have some data (xml, db table, whatever) you wish to convert to an Excel file. A poterntial "mdo:convert-to-xls" method could also return a virtual path of the generated file to the user, but again it would be nice if it could directly stream the file, without saving it on disk.

Do you think this would be useful?

Alberto,

Here is a sample of writing binary data directly to output stream:

<a href="{mdo:service-url('binary')}">binary</a>

<mdo:service name="binary">

  <msxsl:script language="C#" implements-prefix="script">
  <msxsl:assembly name="System.Web" />
  <msxsl:using namespace="System.IO"/>
  <msxsl:using namespace="System.Web"/>
    public int sendData()
    {
      <!-- Generate data somehow -->
      byte[] b = File.ReadAllBytes(@"C:\DotNetNuke\Dev2\Website\logo.gif");
      
      <!-- set correct MIME -->
      HttpContext.Current.Response.ContentType = "image/gif";
      
      <!-- Write data to output stream -->
      HttpContext.Current.Response.OutputStream.Write(b, 0, b.Length);
      return 0;
    }
  </msxsl:script>
  <xsl:template match="/">
  
    <!-- Here you must NOT generate any output -->
    <xsl:execute select="script:sendData()" />
    
  </xsl:template>
  
</mdo:service>

You can use this approach both in inline services (via <mdo:service/>as it is in sample) and when calling XsltDb module by its alias.

 

 

Developer
Dec 17, 2010 at 12:27 PM

This is GREAT :-)