ASP.Net File Upload Module v2 Documentation

Introduction

The ASP.Net file upload/download package consists of an HTTP module that allows direct streaming of upload files from RFC1867 requests and corresponding user interface components which enhance the standard FileUpload component provided by ASP.Net. In addition to this an AJAX based progress bar is displayed to keep the user informed of upload progress. The package is also accompanied by a download handler which allows files to be downloaded directly from Microsoft SQL Server databases when the SQL processor is used.

The component is a self standing ASP.Net 2.0 implementation written in C# 2.0. It supports IIS 6, IIS 7, and the built in Visual Studio web server. The current version of the component does not work in Mono or ASP.Net versions prior to 2.0.

Out of the box, this solution provides a variety or features relating to file uploading and downloading:

  • An HTTP module which allows RFC1867 requests to be parsed with individual files being sent to file processors which can handle them in different ways. The basic implementation supports two file processors: one which stores files on the file system of the web server; and one which uses a SQL Server database table.
  • A small collection of user interface components which provide a replacement for the standard ASP.Net FileUpload component. The replacement DJFileUpload control gives a number of benefits including: multiple uploads; styling of file inputs; validation of file extensions; and, required field validators.
  • Full support for browsers where javascript is disabled including a fallback IFrame based progress bar which does not need script.
  • Complete control over the upload process including access to form fields on the same page as the upload controls very early in the ASP.Net page lifecycle.

The module and UI components have been tested on the following browsers with and without javascript enabled:

  • Internet Explorer 6/7
  • Safari 3.1.2 on OSX
  • Firefox 3.0.1 on OSX
  • Firefox 3.0.1 on Windows
  • Opera 9.5.1 on Windows
  • Opera 9.5.1 on OSX

Prerequisites

This release does not have any prerequisites other than the obvious:

  • ASP.Net 2.0 or above.
  • IIS 6/7 for production deployment.

There are no requirements for any external libraries or components.

Installing the module

Installation of the module is straightforward. The first thing you need to do is download the setup kits from the download page. Once you’ve done this you have the option of setting up a demonstration application or simply installing the file upload library on your web server ready to use from a web application.

By default the installation kit will install the FileUploadLibrary.dll assembly into the GAC. This is required for the module to work in a medium trust environment. You may also use the library from the bin folder of a web application but only in full trust.

Step 1 - install the HTTP module

The HTTP module intercepts incoming file upload requests and sends them off to the selected processors. For example, if you are using the SQL Server processor then the module will split the incoming request and pass each file to the SQL Server processor, which will in turn store the files in the database. To install the module add a reference to FileUploadLibrary.dll in your project and place the following in web.config:

<system.web>
  <httpModules>
    <add name="upload_module" type="darrenjohnstone.net.FileUpload.UploadModule, FileUploadLibrary"/>
  </httpModules>
</system.web>

Step 2 - install the progress handler

The progress bar gets it’s status updates from an HTTP handler which returns an XML message containing the status of the file upload. This is a light weight and efficient mechanism for getting these reports as it returns a very small message pulled directly from memory on the server. To get progress reports the HTTP handler must be installed in web.config:

<system.web>
  <httpHandlers>
	  <add verb="GET" type="darrenjohnstone.net.FileUpload.UploadProgressHandler, FileUploadLibrary" path="UploadProgress.ashx"/>
  </httpHandlers>
</system.web>

Configuration for IIS7

To set up the module and handler for IIS 7, the handlers and module settings are required in the system.webServer section of web.config rather than as above:

<system.webServer>
	<modules>
		<remove name="upload_module"/>
		<add name="upload_module" type="darrenjohnstone.net.FileUpload.UploadModule, FileUploadLibrary"/>
	</modules>
	<handlers>
		<add name="UploadProgress" verb="GET" type="darrenjohnstone.net.FileUpload.UploadProgressHandler, FileUploadLibrary" path="UploadProgress.ashx"/>
  </handlers>
</system.webServer>

Configuring request limits

ASP.Net enforces request limits on applications. These ensure that an individual request never exceeds a pre-set maximum. They exist to prevent denial of service attacks where a bad person can cause your server to go down by frequently sending requests which it can’t handle, thus forcing it to spend most of it’s resources dealing with the problem request to the exclusion of other requests. I think it’s important to mention because request limits aren’t a bad thing- they are an important security feature.

Of course request limits can prevent large files being uploaded, but they’re not the problem. ASP.Net has problems handling large files because it runs out of memory. The upload module solves this by handling memory more efficiently and dealing with files in chunks. The module will still respect the maximum request limits.

In IIS 6 the maximum request limit is set using the maxRequestLength parameter in web.config. In addition to this it is wise to set the executionTimeout parameter to prevent the process from timing out before the file is uploaded. The following example shows how these can be set to 100Mb and 1 hour respectively:

<httpRuntime executionTimeout="3600" maxRequestLength="40960" />

In version 1 of the module the maxRequestLength setting was completely bypassed. In hindsight I think that was probably a bad thing so in version 2 I’ve added in code to ensure that it is respected and that the request is ended if the value is exceeded.

Things are a little different in IIS 7 however. The IIS 7 request filters by default will kick in and limit the maximum content length before the module even gets a chance to do anything. To allow larger uploads we need to set the maximumAllowedContentLength in web.config by entering the statement shown below at a command prompt. This example sets the maximum content length to 100Mb for the web app called “WebApp” on the default web site.


%windir%\system32\inetsrv\appcmd set config "Default Web Site/WebApp" -section:requestFiltering -requestLimits.maxAllowedContentLength:104857600

Note that in IIS 6 the maxRequestLength is in kilobytes while in IIS 7 the maxAllowedContentLength setting is in bytes.

Using the module

Selecting a file processor

Two processors are provided with the upload module- SQLProcessor and FileSystemProcessor, these store uploaded files in a SQL Server database table or in the file system respectively. You also have the option of creating your own processors by implementing the IFileProcessor interface.

For the upload module to work you must select and configure at least one processor.

Selection of a file processor can be accomplished in one of two ways: via global.asax for application level configuration or by setting properties on the DJUploadController or DJFileUpload controls.

Configuration in global.asax

This method is suitable for application level settings. Configuration is carried out using code in the global.asax file executed during the Application_Start event. Here you need to set the processor type and buffer size. You then handle the ProcessorInit event of the UploadManager singleton class in order to set any extra properties (such as a connection string for the SQLProcessor) in your processor when the module initialises it.

void Application_Start(object sender, EventArgs e)
{
    //Uncomment one of the following lines to select a processor

    //UploadManager.Instance.BufferSize = 1024;
    //UploadManager.Instance.ProcessorType = typeof(FileSystemProcessor);

    UploadManager.Instance.ProcessorType = typeof(SQLProcessor);
    UploadManager.Instance.ProcessorInit += new FileProcessorInitEventHandler(Processor_Init);
}

In the Processor_Init event handler you then set up the processor how you want it:

void Processor_Init(object sender, FileProcessorInitEventArgs args)
{
    if (args.Processor is FileSystemProcessor)
    {
        FileSystemProcessor processor;

        processor = args.Processor as FileSystemProcessor;

        // Set up the download path here - default to the root of the web application
        processor.OutputPath = @"c:\uploads";
    }

    if (args.Processor is SQLProcessor)
    {
        SQLProcessor processor;

        processor = args.Processor as SQLProcessor;

        // Set up the connection string
        processor.ConnectionString = "server=(local);initial catalog=FileUpload;integrated security=true";
    }
}

Local configuration via the UI controls

In addition to global set-up via global.asax file processors can be configured individually. You can see how this works in the sample default.aspx.cs page which configures a number of different processors in the page load:

protected void Page_Load(object sender, EventArgs e)
{
    // Set the default processor
    FileSystemProcessor fsd = new FileSystemProcessor();
    fsd.OutputPath = Server.MapPath("~/uploadsdefault");
    DJUploadController1.DefaultFileProcessor = fsd;

    // Change the file processor and set it's properties.
    FileSystemProcessor fs = new FileSystemProcessor();
    fs.OutputPath = Server.MapPath("~/uploads");
    DJFileUpload1.FileProcessor = fs;

    SQLProcessor sql = new SQLProcessor();
    sql.ConnectionConfig = "FUDatabase";
    DJFileUpload2.FileProcessor = sql;
}

This feature allows the use of different controllers on each page and also allows them to be configured later in the page loading cycle than in the previous global.asax implementation.

When this method is used, the parameters are serialised and encrypted at page load. They are then stored in hidden fields on the page so that the module can read them at run-time.

Configuring the SQL Server processor

If you are using the SQL Server processor then set the connection string to your database and create a new table for uploads using this script:

CREATE TABLE [dbo].[UploadedFile](
	[Id] [int] IDENTITY(1,1) NOT NULL,
	[FileName] [varchar](100) NOT NULL,
	[ContentType] [varchar](50) NOT NULL,
	[FileContents] [image] NOT NULL,
 CONSTRAINT [PK_UploadedFiles] PRIMARY KEY CLUSTERED
(
	[Id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

The UploadedFile table contains an identity column which is used to provide a unique identifier to the upload.

Setting up the resources

The upload control requires a number of images, javascript, and CSS files in order to function. These are normally held in the upload_scripts, upload_images, and upload_styles folders which are placed in the root of the web application. Get these folders from the demo application and put them into your web app. The folder structure should look similar to the following image:

If you need to change this then see the next section on configuring the controls.

Configuring the controls

Now that the HTTP module has been configured, any file uploads from standard ASP.Net file inputs will be automatically intercepted and streamed to the chosen provider. However, this is only part of the story. We can also get a progress bar which shows the percentage completed of the uploads and which file the processor is working on at any given time.

A replacement for the standard file input control has also been provided. The new DJFileUpload control can accept multiple file inputs and offers skinning support.

Each page that is to use DJFileUpload controls must have an instance of the DJUploadController control at the top of the page before any upload controls. This control is responsible for emitting all of the required javascript and also for showing the progress bar when the form is submitted.

The DJUploadController control has a number of properties that can be set:

Property Description Default value
ScriptPath The path to the upload_scripts folder which is included in the demo project. This folder contains the scripts needed to make the UI components work. upload_scripts/
ImagePath The path to the upload_images folder which is included in the demo project. This folder contains the images used by the upload skin along with all of the buttons. upload_images/
CSSPath The path to the upload_styles folder which is included in the demo project. This folder contains the styles needed to skin the UI components. upload_styles/
ShowCancelButton Set to true to show a cancel button on the progress bar. The cancel button causes the upload to be terminated when it is clicked. true
ShowProgressBar Set to true to automatically show an upload progress bar when the form is submitted. Set to false to disable the progress bar or to use the DJFileUpload control without the HTTP module. true
AllowedFileExtensions A comma separated list of file extensions to allow in the upload control (e.g. .png,.gif,.jpg) or an empty string to allow all file extensions. An empty string to allow all file extensions.
ApplyStyles true to apply styles to file input controls using javascript. false

In most cases the default values of these properties will suffice. This is especially true if the upload_scripts, upload_images, and upload_styles folders are placed in the root of the web application.

Once the DJUploadController control has been added you can add as many DJFileUpload or normal ASP.Net FileUpload controls as required. The DJFileUpload control has the following configuration properties.

Property Description Default value
InitialFileUploads The number of file boxes to show initially in the upload control. 1
MaxFileUploads The maximum number of files to allow the user to upload via the upload control. 5
ShowAddButton true to show the add button on the control allowing users to add new file boxes. true
ShowUploadButton true to show an upload button on the control which will cause the form to be submitted. In most cases there will be a separate submit button. Any submit button will cause the upload to start. false
ApplyStyles true to apply styles to file input controls using javascript. false

A simple page with a single file upload control would be marked up similar to the following:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="FileUploadV2._Default" %>
<%@ Register assembly="FileUploadLibrary" namespace="darrenjohnstone.net.FileUpload" tagprefix="cc1" %>

<!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 id="PageHeader" runat="server">
    <title>File Upload Demonstration</title>
</head>
<body>
    <form id="MainForm" runat="server">
        <cc1:DJUploadController ID="DJUploadController1" runat="server" ShowCancelButton="true" AllowedFileExtensions=".zip,.jpg,.png" />

        <cc1:DJFileUpload ID="DJFileUpload1" runat="server" ShowAddButton="true" ShowUploadButton="true" />
    </form>
</body>
</html>

Producing a screen like this:

A new configuration section has been added to allow global configuration of the most common file upload settings. These can be overridden by setting the relevent properties in the DJUploadController class. The configuration section is optional.

To use the new configuration section you need to register it in web.config as follows:

<!-- Register the file upload configuration section -->
<configSections>
  <section name="uploadSettings" type="darrenjohnstone.net.FileUpload.UploadConfigurationSection, fileuploadlibrary, Version=2.0.0.0, Culture=neutral, PublicKeyToken=af961ece0b692dfb"/>
</configSections>

You can then set the properties un the uploadSettings section. See the sample application for an example.

<!-- File upload module settings -->
<uploadSettings
  allowedFileExtensions=".pdf,.xls,.doc,.zip"
  scriptPath="/upload_scripts"
  imagePath="/upload_images"
  cssPath="/upload_styles"
  showProgressBar="true"
  showCancelButton="true"
  enableManualProcessing="true"
/>

File input styling

The technique of styling file input controls that I have used may not be suitable for all purposes. For that reason I have made it optional. To turn file input styling on set the ApplyStyles property of the DJFileInput control to true. The default is now false.

Styling turned off Styling turned on

If javascript is not enabled on the browser then styling will be turned off automatically.

Accessibility

This release of the module will work entirely without javascript enabled and degrade gracefully on all browsers I have tested on. This is done by importing a secondary style sheet using javascript which expands all of the file inputs in a control and hides the remove and add buttons.

The accessibility styling changes are accompanied by a non-script iframe based progress bar which kicks in automatically if script is not enabled. This results in a display something similar to the following which is from Opera 9 with script turned off.

As a reminder, the default AJAX based progress bar looks like this:

The basic accessibility features are enabled by default and you don’t have to do anything to make them work. If you want to use the non-script progress bar then you need to place one instance of the DJAccessibleProgressBar control somewhere on the page.

<cc1:DJAccessibleProgressBar ID="DJAccessibleProgrssBar1" runat="server" />

The non-script progress bar needs access to the UploadProgress.aspx page which is included in the project. You can set the URL of this page using the ProgressURL property of the DJAccessibleProgressBar if you need to move it for any reason.

Note that if script is enabled on the browser then the non-script progress bar will automatically remove itself and be replaced with the default modal box version. If you need to use the non-script version even if script is enabled then set the ShowProgressBar property of the DJUploadController to false.

Standard file inputs

Whilst the DJFileUpload control provides a number of advantages over the standard ASP.Net FileUpload control, use of the control is optional. The module will still operate without it. Any ASP.Net FileUpload controls will be processed by the module automatically and the files sent to the default processor which has been configured.

File status information and error handling

Once the upload has completed the Status property of the controller can be used to retrieve a list of all files which were uploaded and any where errors were encountered. The file name is available along with any unique identifier provided by the file processor. In the event of an error the exception is provided.

protected void Page_Load(object sender, EventArgs e)
{
    if (Page.IsPostBack && DJUploadController1.Status != null)
    {
        StringBuilder sb = new StringBuilder();

        sb.Append("<div class='up_results'>");
        sb.Append("<h3>Files uploaded</h3>");
        sb.Append("<ul>");

        foreach (UploadedFile f in DJUploadController1.Status.UploadedFiles)
        {
            sb.Append("<li>");
            sb.Append(f.FileName);

            if (f.Identifier != null)
            {
                sb.Append(" ID = ");
                sb.Append(f.Identifier.ToString());
            }

            sb.Append("</li>");
        }

        sb.Append("</ul>");

        sb.Append("<h3>Files with errors</h3>");
        sb.Append("<ul>");

        foreach (UploadedFile f in DJUploadController1.Status.ErrorFiles)
        {
            sb.Append("<li>");
            sb.Append(f.FileName);

            if (f.Identifier != null)
            {
                sb.Append(" ID = ");
                sb.Append(f.Identifier.ToString());
            }

            if (f.Exception != null)
            {
                sb.Append(" Exception = ");
                sb.Append(f.Exception.Message);
            }

            sb.Append("</li>");
        }

        sb.Append("</ul>");
        sb.Append("</div>");
        ltResults.Text = sb.ToString();
    }
}

In general there is considerably more information available about uploads than in version 1, particularly with the ability for processors to add their own identification information which made the SQLProcessor possible.

Accessing form fields in file processors

In version 2 of the module it is now possible to access user defined fields on the upload form from your IFileProcessor implementation. Up to now this has been accomplished by collecting custom fields on a preceeding page and pass them into the upload page via request variables- this is still valid but doesn’t allow for the controls to all be on the same page.

All fields on the form are parsed by the upload module (file uploads or otherwise) but non-file upload fields are not available until the module has finished processing.

Version 2 has logic into the form processor which stores the names and values of all form fields as it goes. These are then passed into the IFileProcessor which can use them as it sees fit. The only restriction is that the IFileProcessor will only have access to fields which preceeded the corresponding file input on the form.

To accomplish this the IFileProcessor interface has been changed very slightly. The StartNewFile method now has a new parameter PreviousFields which is a dictionary of all fields that have been parsed so far.

        /// <summary>
        /// Starts a new file.
        /// </summary>
        /// <param name="fileName">File name.</param>
        /// <param name="contentType">The content type of the file.</param>
        /// <param name="headerItems">A dictionary of items pulled from the header of the field.</param>
        /// <param name="previousFields">A dictionary of previous fields.</param>
        /// <returns>An optional object used to identify the item in the storage container.</returns>
        object StartNewFile(string fileName, string contentType, Dictionary<string, string> headerItems, Dictionary<string, string> previousFields);

To take a trivial example, imagine that there were to be a drop down list of prefixes which must be selected for an upload control. When the file was created, the prefix would be applied to the file name. This is an example of information which is not available until the form is processed.

To accomplish this a drop down list called lstFilePrefix is placed on the form before the related file upload control. Then in the IFileProcessor the prefix can be applied with the following code by checking the PreviousFields dictionary for the control ID of the list.

/// <summary>
/// Starts a new file.
/// </summary>
/// <param name="fileName">File name.</param>
/// <param name="contentType">The content type of the file.</param>
/// <param name="headerItems">A dictionary of items pulled from the header of the field.</param>
/// <param name="previousFields">A dictionary of previous fields.</param>
/// <returns>An optional object used to identify the item in the storage container.</returns>
public object StartNewFile(string fileName, string contentType, Dictionary<string, string> headerItems, Dictionary<string, string> previousFields)
{
    string prefix = String.Empty;

    _errorState = false;
    _headerItems = headerItems;

    // Get the prefix from the drop down list
    if (previousFields.ContainsKey("lstFilePrefix"))
    {
        prefix = previousFields["lstFilePrefix"];
    }

    try
    {
        _fileName = fileName;
        _fullFileName = _outputPath + prefix + Path.GetFileName(fileName);
        _fs = new FileStream(_fullFileName, FileMode.Create);
    }
    catch (Exception ex)
    {
        _errorState = true;
        throw ex;
    }

    return null;
}

I’ve provided an example processor (FieldTestProcessor.cs) in the library.

What happens if the module is not installed?

If the upload module is not installed the upload controls will still operate correctly. In this instance the DJUploadController control will inspect all file inputs and pass the uploaded files directly to the appropriate file processors.

Getting help

If you need help using the module or you find any issues then send me a mail at darren@darrenjohnstone.net or post a comment.

Other information

Credits

Thanks to those who have taken the time to install and test the beta versions of the module. The feedback and bug reports have been invaluable in reaching a final release.

The styling of file inputs is based on a method found on www.quirksmode.org.

The AJAX progress bar popup uses the excellent modalbox javascript library.

A special thanks goes to Dean Brettle who is the author of Neat Upload for his help with this version. In particular the encryption logic for storing the settings of upload controls in form fields is taken from Neat Upload (although the actual implementation is different and handled by the form parser).