Rick Strahl's Weblog
Rick Strahl's FoxPro and Web Connection Weblog
White Papers | Products | Message Board | News |

Http File Uploads in Classic and Web Control Framework Web Connection


July 31, 2008 •

A question came up today on how to handle file uploads in the context of a Web Control Framework page in Web Connection. Http File uploads are a common HTTP construct and Web Connection has long supported this process in its classic mode of operation.

 

Classic Http File Uploads

 

In classic Web Connection applications file uploads are pretty much handled through the raw mechanics of the Http protocol which involves basically two steps:

 

  • A <form> tag that incudes an enctype of multiplart/form-data
  • An <input type="file" /> control that allows the user to select the file to upload

Uploads are typically handled through a separate form (or a single form) that posts back to the server via a special enctype of multipart/form data, which is required in order to force the content to be uploaded to the server.

 

Here's what the HTML of this process looks like:


<form enctype="multipart/form-data" method="post" action="/wconnect/FileUpload.wwd">

    <p>

        <input type="file" size="20" name="File"/><br/>

        <br/>

        File Description:<br/>

        <textarea cols="43" name="txtFileNotes" rows="4"/><br/>

        <input type="submit" name="btnSubmit" value="Upload File"/>

    </p>

</form>

 

To pick up the file on the server, you can simply use the standard wwRequest object which automatically detects the content type of the posted data and returns Form data appropriately for this content. To retrieve the file and its name a little more work is required by calling the special Request.GetMultipartFile() to retrieve the file. All other form vars can be retrieved using the standard Request.Form() method.

 

Here's is the Fox code to pick up the file in a classic Process method:

 

FUNCTION FileUpload

************************************************************************

* wwDemo :: FileUpload

*********************************

***  Function: Demonstrates how to upload files from HTML forms

***            This function requires that multipart forms are

***            used on the client (multipart/data)

***************************************************************************

 

lcFileName = ""

 

*** This works too but doesn't retrieve the file name

* lcFileBuffer = Request.Form("File")

 

*** Files must use GetMultipartFile to retreive the file name as well

lcFileBuffer = Request.GetMultiPartFile("File",@lcFileName)

lcNotes = Request.Form("txtFileNotes")

 

lcFileName = SYS(2023)+"\"+lcFileName

 

IF LEN(lcFileBuffer) = 0

   THIS.StandardPage("Upload Error","An error occurred during the upload or the file was too big.")

   RETURN

ENDIF

IF LEN(lcFileBuffer) > 500000

   THIS.StandardPage("File upload refused",;

                     "Files over 500k are not allowed for this sample...<BR>"+;

                     "File: " + lcFilename)

   RETURN

ENDIF

  

*** Now dump the file to disk -  for whatever purpose

File2Var(lcFileName,lcFileBuffer)

 

THIS.StandardPage("Thank you for your file upload",;

                  "<b>File Uploaded:</b> " + lcFileName + ;

                  " (" + TRANSFORM(FileSize(lcFileName),"9,999,999") + " bytes)<p>"+ ;

                  "<b>Notes:</b><br>"+ CRLF + lcNotes )

 

ENDFUNC

* wwDemo :: FileUpload

 

Nothing fancy here. Everything works as with standard forms except for the call to GetMultipartFile() which retrieves the file and filename. Note that the filename is passed by reference so that the value can be updated by the function.

Uploading Files in the Web Control Framework

The process for Web Control Framework pages is actually quite similar, but it's maybe even easier because the framework automatically handles setting the content type of the form for you. The Web Control framework contains a wwWebFileUpload control which you can place on a form and it automatically manipulates the form to switch the form into multipart mode.

 

The following example is an image uploader application that allows uploading and immediately displaying the uploaded image in the Web page:

 

<%@ Page Language="C#"

      ID="UploadImage_Page"

      GeneratedSourceFile="webcontrols\UploadImage_page.prg"         

%>

<%@ Register Assembly="WebConnectionWebControls"

    Namespace="Westwind.WebConnection.WebControls"

    TagPrefix="ww" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

    <title>Image Upload Example</title>

    <link href="westwind.css" rel="stylesheet" type="text/css" />

</head>

<body style="margin-top:0px;margin-left:0px">

    <form id="form1" runat="server">

 

    <h1>Image Upload Example</h1>

   

    <ww:wwWebErrorDisplay runat="server" id="ErrorDisplay" />

 

    <div class="notebox">

        This example demonstrates how to upload a file inside from within a Web Form

        and retrieve it on the server. Note that the form must be submitted

        with enctype="multipart/form-data" in order for uploads to work!

    </div>

 

    <div class="containercontent">

        Please pick an image to upload:<br />

        <ww:wwWebFileUpload ID="FileUpload" runat="server"  style="width:400px"/>

       

        <br />       

        <br />

        Leave a note about the file:<br />

        <ww:wwWebTextBox ID="txtFileName" runat="server" 

                         TextMode="MultiLine" Width="400px"

        />

        <br />

        <ww:wwWebButton ID="wwWebButton1" runat="server"

                        Click="btnSubmit_Click"

                        Text="Upload" width="80" />

        <hr />

       

        <ww:wwWebImage runat="server" id="imgPreview"

                       style="float: left; margin-right: 10px;" />

       

        <small>

            <ww:wwWebLabel runat="server" ID="lblCaption" />

        </small>

    </div>

    </form>

</body>

</html>

 

Note that the form is NOT set up with enctype="multipart/form-data" although if you do add that manually that will work too. It's just not required here. Note that the upload control MUST live inside of the <form runat="server"> tag block.

 

Other than that it's just a normal input form. To capture the file now you can use code like the following in the button submit:

 

************************************************************************

*  btnSubmit_Click

****************************************

***  Function:

***    Assume:

***      Pass:

***    Return:

************************************************************************

FUNCTION btnSubmit_Click()

LOCAL lcFileName, lcContent, lcNotes

 

*** Pick up content and filename

lcFilename = ""

lcContent = Request.GetMultiPartFile("FileUpload",@lcFileName)

lcNotes = this.txtFileName.Text

 

lcExt = LOWER(JUSTEXT(lcFileName))

 

IF EMPTY(lcExt) OR NOT lcExt $ "jpg|jpeg|png|gif|tif|bmp"

      this.ErrorDisplay.ShowError("Invalid Image type uploaded")

      RETURN

ENDIF

IF LEN(lcContent) > 500000

      this.ErrorDisplay.ShowError("Image uploaded is too large.")

      RETURN     

ENDIF

 

 

*** Generate output filename and full output path

*** Note reading from demo's cHtmlPagePath from .ini file

lcSaveFileName = "_timg_" + lcFileName 

lcSaveFullPath = Server.oConfig.owwDemo.cHtmlPagePath +;

                "temp\" + lcSaveFileName

 

*** Dump the file content string to a file

STRTOFILE( lcContent, lcSaveFullPath )

 

*** Assign the Web based file path to the image to display

this.imgPreview.ImageUrl = "~/temp/" + lcSaveFileName

 

this.lblCaption.text = lcNotes + ;

                  "<hr><i>" + lcFileName + "<br />" + ;

                  "Size: " + TRANS(LEN(lcContent)) + " bytes</i>"

 

*** Clean up files that are older than 120 seconds

DeleteFiles("..\temp\_timg*.*",120)

 

ENDFUNC

*   btnsubmit_Click()

 

 

This code is more complete than the last example, but it basically picks up the uploaded file by its form variable name in this case FileUpload. The function returns the file content as a binary string. This output can then simply be written to disk after ensuring that it's of the right type.

 

In this case the file is written to disk in a Web directory and then displayed in an image control, whose ImageUrl is set dynamically with the new name of the file. Obviously you may want to do something different with this file like store it in a database or store it in some other dir and never display it.

 

If you do chose the file approach to dump to disk and display the image remember that you'll need to clean up the generated files as the filenames are unique as to avoid name overlap. The DeleteFiles() function from wwUtils makes this task very easy – it simply deletes files that are older than a certain number of seconds which serves as a brute force cleanup routine for 'temporary' files.

 

Looking over this code it also occurred to me that it'd be useful to have a method on the upload control that can retrieve the file and filename. This would avoid having to know the POST variable name (which may be mangled due to naming containers). So I added a new method to wwWebFileUpload called .GetUploadedFile(@lcFileName) that basically makes a call through the Request.GetMultipartFile(). While no easier than the Request method, the method on the control should make it easier to discover how to actually pick up the file once it's been uploaded. With it the code will change to:

 

*** Pick up content and filename

lcFilename = ""

lcContent = this.FileUpload.GetUploadedFile(@lcFileName)

lcNotes = this.txtFileName.Text

 

which is a little cleaner and certainly easier to discover.

 

There you have it. Nothing difficult about this process, but it is a little different for the two mechanisms.

Posted in:

Feedback for this Weblog Entry