NeatUpload Manual

Release Notes for NeatUpload-1.1.x

Upgrading from NeatUpload-1.0.x

NeatUpload-1.1.x is intended to be almost completely backward-compatible with NeatUpload-1.0.x.  The only exception is that ProgressBars must now be placed somewhere inside a <form> element.  In almost all cases that will already be the case so this should not be a major issue.  

To upgrade, simply replace your Brettle.Web.NeatUpload.dll with the copy from NeatUpload-1.1.x and add Progress.js and Error413.aspx from NeatUpload-1.1.x/NeatUpload/ to the NeatUpload/ subfolder in your web application.  If you have not customized your Progress.aspx page, you should replace it with NeatUpload-1.1.x/NeatUpload/Progress.aspx.  If you have customized it, consider changing it to use the new <Upload:DetailsSpan> and <Upload:DetailsDiv> elements to take advantage of AJAX refreshless updates.  See Customizing Progress.aspx for details.

If you recompile, you will see some warnings about obsoleted/deprecated methods.  Those methods may be removed in NeatUpload-2.x.  You are encouraged to switch to their replacements, but that is not required for NeatUpload-1.1.x.  Similarly, you are encouraged to use the new custom configuration section instead of the NeatUpload-1.0.x appSettings.  The old appSettings still work in NeatUpload-1.1.x, but the new custom configuration section provides much more flexibility, including the ability to use location filtering to restrict the requests that NeatUpload touches.  See Configuration for details.

New Features in NeatUpload-1.1

New features for end-users: New features for developers:

Introduction

NeatUpload allows ASP.NET developers to stream uploaded files to disk and allows users to monitor upload progress. It is open source and works under Mono's XSP / mod_mono as well as Microsoft's ASP.NET implementation.

NeatUpload contains four custom controls (InputFile, ProgressBar, DetailsSpan, and DetailsDiv), an HttpModule (UploadHttpModule), and a Page subclass (ProgressPage). This section briefly describes what they each do and how they are related. The remainder of this manual describes how to install and use NeatUpload.

InputFile is a custom control that renders like HtmlInputFile, but provides properties to access the uploaded file's client-specified name, content, and MIME type, and a method to move the file to a permanent location.

ProgressBar is a custom control that is responsible for providing a site for the progress to be displayed. It provides an attribute to control whether to display the progress inline or in a popup. It also provides ways to control which buttons cause the progress display to start refreshing. If the progress is displayed inline, the ProgressBar control renders as an IFRAME. Otherwise, it renders as a DIV containing a "Check Upload Progress" link (to display the progress in a new window) along with some JavaScript which removes the DIV when the page loads. This provides a fallback when JavaScript is not available. Note that ProgressBar doesn't actually display the progress bar itself. It merely provides a site (an IFRAME or popup) which loads a page derived from ProgressPage (by default Progress.aspx).  The ProgressPage subclass displays the progress bar.

UploadHttpModule is an HttpModule that intercepts HTTP requests, streams InputFile uploads to temporary files, and restricts the size of the remainder of the request. By default, UploadHttpModule intercepts every request.  As a result, a bug in NeatUpload could affect pages that don't even contain InputFile controls. For this reason, you can configure NeatUpload to only use UploadHttpModule for certain pages (or even none) while continuing to use InputFile and ProgressBar. When UploadHttpModule is not used for a request, NeatUpload gets the uploaded file from the ASP.NET standard HttpRequest.Files property instead of intercepting the request. That means that InputFile will function like an HtmlInputFile control, but no progress bar will be displayed. This greatly reduces the risk of adopting NeatUpload.

ProgressPage is a subclass of System.Web.UI.Page that is used as the base class for pages displayed in the site provided by the ProgressBar control (i.e. in the IFRAME or popup).  Progress.aspx is the default ProgressPage.  ProgressPage retrieves the details of the upload process from the UploadHttpModule. and makes them available for subclasses to use in data-binding expressions.  ProgressPage subclasses (e.g. Progress.aspx) place such data-binding expressions within DetailsSpan and DetailsDiv controls so that NeatUpload can use AJAX techniques to update the values without requiring a browser a refresh.

Requirements

To use NeatUpload you will need:

Installation

Installing NeatUpload is pretty straightforward. Just copy the necessary assemblies and subdirectories into your application and make some modifications to your Web.config.  Specifically:

  1. If you haven't already, download the release and extract it wherever you want. All the files will be extracted into a subdirectory named NeatUpload-1.1.x/.
  2. (Optional) To see the demo running on your local machine, configure IIS, mod_mono, or XSP so that NeatUpload-1.1.x/ is a web application. Then point your browser at the Demo.aspx on that website. To use NeatUpload in your own web application, continue with the remaining installation steps.
  3. If you will be using the Visual Studio Web Form Designer, add the NeatUpload controls to your toolbox.  To do that, right-click on the Toolbox, click Add/Remove Items, Browse to  NeatUpload-1.1.x/bin/Brettle.Web.NeatUpload.dll, click Open, and then click OK.  A reference to Brettle.Web.NeatUpload.dll will automatically be added to your project the first time you use the designer to add one of the NeatUpload controls to a form.
  4. If you won't be using the Visual Studio Web Form Designer, add a reference to your web application that refers to the Brettle.Web.NeatUpload.dll in the NeatUpload-1.1.x/bin directory.
  5. Create a NeatUpload/ subdirectory in your web application by copying NeatUpload-1.1.x/NeatUpload/ and its contents.  Don't copy the whole NeatUpload-1.1.x/ directory, just the NeatUpload-1.1.x/NeatUpload/ subdirectory.  That subdirectory contains Progress.aspx and associated files.
  6. Add the following line to your Web.config under configuration/system.web/httpModules:
    <add name="UploadHttpModule" type="Brettle.Web.NeatUpload.UploadHttpModule, Brettle.Web.NeatUpload" />
  7. (Optional) To allow larger uploads than the default of 4 Mbytes under Microsoft's ASP.NET,  you might need to add or modify the following element in your Web.config under configuration/system.web:
    <httpRuntime maxRequestLength="size_in_kbytes" />
    At the moment, both .NET and Mono seem to ignore that setting when NeatUpload is being used, but there is no official documentation specifying exactly when that limit is enforced, so a future version of .NET or Mono might enforce the limit even when NeatUpload is being used.   Setting the <httpRuntime> element's maxRequestLength attribute simply provides insurance against such future changes.  Note that since that attribute is currently ignored when NeatUpload is used, you can't use it to actually restrict the size of uploads.  To do that, see Limiting the Size of Upload Requests below.
  8. (Optional) Copy Demo.aspx and Demo.aspx.cs from NeatUpload-1.1.x/ into your application. Point your browser at Demo.aspx and verify that the demo functions properly.

Usage

The following instructions should work with Visual Studio 2003 and Visual Studio 2005.  If you are using Visual Studio 2002, you will need to convert the NeatUpload projects/solution to the Visual Studio 2002 format, make some code changes, and rebuild the NeatUpload solution.  There is a forum thread discussing the process in more detail.

Using NeatUpload with the Visual Studio Web Form Designer

To use the Visual Studio web form designer to add NeatUpload to a web form, follow these steps:

  1. Drag the following controls from the toolbox onto your web form: InputFile, ProgressBar, and Button.
  2. (Optional) If you want an inline progress bar, set the ProgressBar's Inline property to true.
  3. (Optional) If you want to customize the ProgressBar fallback behavior, drag whatever control(s) you want displayed as a fallback onto the ProgressBar control.  For example, to just change the fallback text, you could drag a Label control onto the ProgressBar and edit its contents.
  4. In your codebehind file, process the uploaded file.  The uploaded file's client-specified name, MIME type, and contents can be accessed via inputFileId.FileName, inputFileId.ContentType, and inputFileId.FileContent, respectively.  If you want to keep the uploaded file, you must use the inputFileId.MoveTo()method to move the uploaded file to a permanent location.  If you do not, NeatUpload will automatically remove the uploaded file at the end of the request to ensure that unwanted files are not left on the filesystem.  The following code will put the uploaded file in the application's root directory (assuming sufficient permissions):
    using Brettle.Web.NeatUpload;
    ...
    using System.IO;
    ...
    public class YourPage : System.Web.UI.Page
    {
    private void Page_Load(object sender, EventArgs e)
    {
    ...
    submitButtonId.Click
    += new System.EventHandler(this.Button_Clicked);
    ...
    }
    ...
    private void Button_Clicked(object sender, EventArgs e)
    {
    ...
    if (IsValid && inputFileId.HasFile)
    {
    ...
    inputFileId.MoveTo(Path.Combine(Request.PhysicalApplicationPath, inputFileId.FileName), 
    MoveToOptions.Overwrite);

Using NeatUpload without the Visual Studio Web Form Designer

To use NeatUpload on a web form without using the Visual Studio designer, follow these steps:

  1. Add the following to the top of your page:
     
    <%@ Register TagPrefix="Upload" Namespace="Brettle.Web.NeatUpload" Assembly="Brettle.Web.NeatUpload" %>
  2. Add an InputFile control to your aspx page wherever you want the user to choose a file, using something like this:
    <Upload:InputFile id="inputFileId" runat="server" />
    Feel free to add any attributes that you would normally add to an HtmlInputFile tag.
  3. Add a ProgressBar control to your aspx page wherever you want to display an inline progress bar or, in the case of a popup progress bar, add it wherever you want the fallback link to be displayed.  Use something like this:
    <Upload:ProgressBar id="progressBarId" runat="server" inline="true|false" />
    The inline attribute defaults to false.
  4. Add a submit button to your aspx page. For example:
    <asp:Button id="submitButtonId" runat="server" Text="Submit" />
  5. In your codebehind file, declare each InputFile, ProgressBar, and Button control using code like this:
    using Brettle.Web.NeatUpload;
    ...
    using System.Web.UI.WebControls;
    ...
    public class YourPage : System.Web.UI.Page
    {
    ...
    protected InputFile inputFileId;
    protected ProgressBar progressBarId;
    protected Button submitButtonId;
  6. In your codebehind file, process the uploaded file.  The uploaded file's client-specified name, MIME type, and contents can be accessed via inputFileId.FileName, inputFileId.ContentType, and inputFileId.FileContent, respectively.  If you want to keep the uploaded file, you must use the inputFileId.MoveTo()method to move the uploaded file to a permanent location.  If you do not, NeatUpload will automatically remove the uploaded file at the end of the request to ensure that unwanted files do not fill up the filesystem.  The following code will put the uploaded file in the application's root directory (assuming sufficient permissions):
    using Brettle.Web.NeatUpload;
    ...
    using System.IO;
    ...
    public class YourPage : System.Web.UI.Page
    {
    private void Page_Load(object sender, EventArgs e)
    {
    ...
    submitButtonId.Click += new System.EventHandler(this.Button_Clicked);
    ...
    }
    ...
    private void Button_Clicked(object sender, EventArgs e)
    {
    ...
    if (IsValid && inputFileId.HasFile)
    {
    ...
    inputFileId.MoveTo(Path.Combine(Request.PhysicalApplicationPath, inputFileId.FileName), 
    MoveToOptions.Overwrite);

Avoiding Unnecessary Uploads and Progress Displays

By default, anytime the user submits a form containing a non-empty InputFile and a ProgressBar, the progress display is started (either inline or in a popup).  If all InputFiles are empty, the progress display is not started.  That ensures that the user is not distracted by a transient and meaningless progress display.

Now consider the case where the form contains a cancel button in addition to a normal submit button.  Both buttons cause the form to be submitted, but when the cancel button is clicked, the server ignores the values on the form.  By default, if the user selects a file to upload and then changes his mind and clicks the cancel button, the progress display will start and the user will need to wait for the file to be uploaded.  To avoid this unfriendly behavior, NeatUpload allows you to specify "trigger" controls.  If you specify at least one trigger, then form submissions initiated via any other control will cause NeatUpload to clears all the InputFile controls so that no files will be uploaded and the progress display will not start.  (On some downlevel browsers, notably Internet Explorer for the Mac, NeatUpload can't clear the controls so it displays a dialog asking the user to clear them manually and try again.  The text for that dialog can be customized via the ClearFileNamesAlert resource in Strings.resx.)

If your form contains more than one ProgressBar, each ProgressBar which specifies trigger controls will only start when the form submission is initiated by one of those controls.

There are two ways to indicate that a control is a "trigger" control.  You can either include the ID of the control in the ProgressBar's Triggers property (multiple IDs should be space-separated), or you can call the ProgressBar's AddTrigger(Control) method.  Note: if you call the AddTrigger(Control) method, you need to call it each time the page is loaded (even when Page.IsPostBack is true) because the list of trigger controls is not maintained in the page's ViewState.

Configuration

Using the <neatUpload> Configuration Section

To configure NeatUpload, you need to add the NeatUpload configuration section handler to your Web.config:

<configuration>
    <configSections>
        <sectionGroup name="system.web">
            <section name="neatUpload" type="Brettle.Web.NeatUpload.ConfigSectionHandler, Brettle.Web.NeatUpload" allowLocation="true" />
        </sectionGroup>
    </configSections>
...


Note: The sectionGroup "name" attribute can be either "system.web" or "brettle.web".  Although using "brettle.web" is better for preventing name collisions, it makes it impossible to do location filtering under Mono (at least as of 1.1.9.2).  Even under .NET, you will have to do more typing to configure location filtering if you use "brettle.web" than if you use "system.web".  The remainder of this document assumes you are using "system.web".

Once you've added the configuration section handler, you can add the <neatUpload> element inside your Web.config's <system.web> element(s), like this:

<configuration>
    ...
    <system.web>
        ...
        <neatUpload
            useHttpModule="true or false, defaults to true"
            maxNormalRequestLength="
up to 2097151 in KBytes, defaults to 4096"
            maxRequestLength="
up to 2097151 in KBytes, defaults to 2097151"
            defaultProvider="friendly name, defaults to a FilesystemUploadStorageProvider using the system temp dir">
            <providers>
                <add name="friendly name"
                     type="type derived from Brettle.Web.NeatUpload.UploadStorageProvider"
                     provider-specific-attributes ... />
                <remove name="friendly name of provider" />
                <clear />
            </providers>
        </neatUpload>
        ...
    </system.web>
    ...
</configuration>

The <providers> element is optional.  You only need to use it if you are using an UploadStorageProvider other than the default FilesystemUploadStorageProvider, or you need to change the temporary directory used by the FilesystemUploadStorageProvider.  In those cases, you should include an <add> element for each provider or provider configuration you will be using and set the <neatUpload> element's defaultProvider attribute to the name you chose for the provider you want to use by default.  For specific pages or directories, you can override that default or add/remove/clear available providers using child configuration sections.  Child configuration sections are <neatUpload> elements within <location> elements or in Web.config files in subdirectories.

Note that each provider object is created and initialized at most once during the application lifecycle - when the configuration section containing the associated <add> element is relevant to a request.  A configuration section is relevant to a request if the request matches the "path" attribute of the containing <location> element.  If there is no containing <location> element, the configuration section is relevant if the requested page is in the directory hierarchy controlled by the containing Web.config.  This means that multiple provider objects might be created and initialized in response to a request, but only the one configured as the "defaultProvider" for the requested page is actually used for that request.  The others might be configured as default providers for other pages.

Changing the Temporary Directory Used by the FilesystemUploadStorageProvider

By default, NeatUpload puts uploaded files in temporary files in the system's temporary directory. You can specify a different directory using the tempDirectory attribute of the FilesystemUploadStorageProvider, like this:

	<neatUpload ... defaultProvider="FilesystemUploadStorageProvider" ...>
            <providers>
...
                <add name="
FilesystemUploadStorageProvider"
                     type="Brettle.Web.NeatUpload.
FilesystemUploadStorageProvider"
                     t
empDirectory="path, defaults to path returned by System.IO.Path.GetTempPath()" ... />
...
            </providers>
</neatUpload>
If you specify a relative path, it will be relative to your application root directory.

Using Location Filtering to Restrict/Modify NeatUpload's Request Processing

By default, whenever NeatUpload's UploadHttpModule is added via the <httpModules> section of your Web.config, it intercepts and filters all requests sent to your application.  For upload requests, it streams the uploaded files to disk and makes them available to your code via InputFile.  It also limits the size of the non-upload part of the request and the total size of non-upload requests because that request data is stored in server memory.  If it didn't do that an attacker could mount a Denial of Service attack by sending a request that contains up to maxRequestLength kilobytes of non-file data.

You can disable use of UploadHttpModule by settting the <neatUpload> element's useHttpModule attribute to false.  If you want to use the UploadHttpModule for only some requests, you can use ASP.NET's <location> element to specify which pages it should be used for.  You can also use the <location> element to control which UploadStorageProvider is used for specific pages.  For example, consider the following configuration:

<configuration>
    ...
    <system.web>
        ...
        <httpRuntime maxRequestLength="100" />
        ...
        <neatUpload useHttpModule="false" maxNormalRequestLength="100" maxRequestLength="2097151">
            <providers>
                <add name="special" type="Brettle.Web.NeatUpload.FilesystemUploadStorageProvider"
                    tempDirectory="SpecialTempDirectory" />
            </providers>
        </neatUpload>
        ...
    </system.web>
    ...
    <location path="Demo.aspx">
        <system.web>
            <neatUpload useHttpModule="true" />
            <httpRuntime maxRequestLength="2097151" executionTimeout="3600" />
        </system.web>
    </location>
    <location path="Special.aspx">
        <system.web>
            <neatUpload useHttpModule="true" defaultProvider="special" />
            <httpRuntime maxRequestLength="2097151" executionTimeout="3600" />
        </system.web>
    </location>
    ...
</configuration>

That configuration will cause the UploadHttpModule to only be used for requests to Demo.aspx and Special.aspx.  In addition, ASP.NET's maxRequestLength is set to only 100 KBytes for all requests other than those two pages.  For those two pages, the maxRequestLength is increased to 2 GBytes and the executionTimeout is increased to 1 hour.  Also, requests for Special.aspx use the "special" provider which is configured to use the "SpecialTempDirectory", while requests for Demo.aspx use the default provider which uses the system temporary directory.

Limiting the Size of Upload Requests

By default, NeatUpload does not directly limit the size of uploads.  To limit upload size, use the <neatUpload> element's maxRequestLength attribute:

	<neatUpload ... maxRequestLength="sizeInKBytes" ... />

If a user attempts to upload a file larger than the specified size, NeatUpload will update the progress bar to indicate that the upload was rejected because it was too large.  If the progress bar is in a pop-up window, it will leave the window open to ensure that the user sees why the upload was rejected.   Next it will attempt to stop the upload by asking the browser to run JavaScript that simulates clicking the browser's Stop button.  If JavaScript is enabled, most modern browsers (including recent versions of IE, Firefox, and Opera) will be able to stop the upload and the user will simply see the original form along with the progress bar displaying the reason the upload was rejected.  The remainder of this section only affects the user when the browser is not able to stop the upload.

After asking the browser to stop the upload using JavaScript, NeatUpload reads the remainder of the request and then throws an UploadTooLargeException .  The UploadTooLargeException class is a subclass of UploadException (which is a subclass of HttpException) with an HTTP status code of 413.  If the browser hasn't stopped the upload for some reason, the exception will cause the user to see a generic error page by default.  If you want something more user-friendly, you can either handle the error by adding a handler for the HttpApplication.Error event, or you can use a custom error page.  NeatUpload comes with an example custom error page.  To use it, add the following to your Web.config under configuration/system.web:

	<customErrors mode="On">
<error statusCode="413" redirect="~/NeatUpload/Error413.aspx" />
</customErrors>

Note: most browsers, including IE and Firefox, won't display any new content until the entire upload has been sent to the server.  If that takes longer than the amount of time specified via the httpRuntime element's executionTimeout attribute, the server may choose to close the connection and the user will see a different error.  IE will display a generic error page saying "Page cannot be displayed".  Firefox will display a dialog saying "Document contains no data".  The default executionTimeout is 360 seconds.

Changing the Maximum Size of Normal Requests

By default, NeatUpload restricts the size of non-upload requests and the non-upload portion of upload requests to 4 MBytes. To specify a different maximum size, use the <neatUpload> element's maxNormalRequestLength attribute:

	<neatUpload ... maxNormalRequestLength="sizeInKBytes" ... />
The behavior when this value is exceeded is similar to the behavior when the maxRequestLength is exceeded.  See the preceding section for details.  The only difference is that for non-upload requests that exceed the maxNormalRequestLength limit, NeatUpload does not attempt to use JavaScript to interrupt the browser and the exception that is thrown is an HttpException(413, "Request Entity Too Large") instead of an UploadTooLargeException().

The HashedInputFile Extension

HashedInputFile is a NeatUpload extension that computes the cryptographic hash of each uploaded file while the upload is in progress.   It consists of the HashedInputFile web control and a custom UploadStorageProvider called HashingFilesystemUploadStorageProvider.  The HashedInputFile control is a subclass of InputFile that adds a Hash property which returns the cryptographic hash of the uploaded file computed by the HashingFilesystemUploadStorageProvider.  To use the HashedInputFile extension:
  1. Install HashedInputFile:
    If you will be using the Visual Studio Web Form Designer, add the HashedInputFile control to your toolbox.  To do that, right-click on the Toolbox, click Add/Remove Items, Browse to  NeatUpload-1.1.x/HashedInputFile/bin/Brettle.Web.NeatUpload.HashedInputFile.dll, click Open, and then click OK.  A reference to Brettle.Web.NeatUpload.HashedInputFile.dll will automatically be added to your project the first time you use the designer to a HashedInputFile to a form.
    If you won't be using the Visual Studio Web Form Designer, add a reference to your web application that refers to the Brettle.Web.NeatUpload.HashedInputFile.dll in the NeatUpload-1.1.x/HashedInputFile/bin directory.
  2. Configure your web application to use the HashingFilesystemUploadStorageProvider.  The simplest way to do that is to add the following to your Web.config (see Configuration for more options):

    <configuration>
        <configSections>
            <sectionGroup name="system.web">
                <section name="neatUpload" type="Brettle.Web.NeatUpload.ConfigSectionHandler, Brettle.Web.NeatUpload"                                    allowLocation="true" />
            </sectionGroup>
        </configSections>
        ...
        <system.web>
            ...
            <neatUpload
                defaultProvider="HashingFilesystemUploadStorageProvider">
                <providers>
                    <add name="
    HashingFilesystemUploadStorageProvider"
                     type="Brettle.Web.NeatUpload.HashingFilesystemUploadStorageProvider, Brettle.Web.NeatUpload.HashedInputFile"
                     algorithm="MD5"/>
                </providers>
            </neatUpload>
            ...
        </system.web>
        ...
    </configuration>

    Note: the value of the algorithm attribute above is passed to HashAlgorithm.Create().  The default value is "MD5", so the attribute could have been omitted above.
  3. Add the HashedInputFile to your forms:
    If you are using Visual Studio Web Form Designer, simply drag the HashedInputFile control from your Toolbox and drop it on the form.
    If you are not using Visual Studio Web Form Designer, add the following line at the top of your form:

    <%@ Register TagPrefix="HashedUpload" Namespace="Brettle.Web.NeatUpload" Assembly="Brettle.Web.NeatUpload.HashedInputFile"%>

    and add an HashedInputFile control to your aspx page wherever you want the user to choose a file, using something like this:
    <HashedUpload:HashedInputFile id="inputFileId" runat="server" />
  4. Set any properties that you would normally set on an InputFile or HtmlInputFile control.
  5. In your code-behind class, use the inputFileId.Hash property to get a byte array containing the cryptographic hash.  You can also use inputFileId.HashSize to get the number of bits in the hash (in case it isn't a multiple of 8).
For an example of using the HashedInputFile extension, see HashedInputFile/HashedDemo.aspx and HashedInputFile/HashedDemo.aspx.cs.  For a more complex configuration example, see HashedInputFile/Web.config.  It uses location filtering and is designed to work in conjuction with the Web.config in parent directory.

Customization

Customizing Progress.aspx

The Progress.aspx page that comes with NeatUpload is just an example. You can modify it to suit your needs.  For example, you could change the color scheme with minor modification to the default.css stylesheet that is included, or you could change the text or images that are used by modifying Progress.aspx itself.  You could even rearrange the layout entirely or create a new page derived from ProgressPage and use the Url property of the ProgressBar to refer to it.  To allow AJAX refreshless updates, you must include at least one DetailsSpan or DetailsDiv control.  Those controls render as like div and span controls, respectively.  Use data-binding expressions within those controls (either the in the content or properties) to access the details of the upload at runtime.  Below is a list of the properties and methods that NeatUpload provides for use in data-binding expressions.   Refer to the default Progress.aspx for an example of how each of these is used.

Name Description
Status A value from the UploadStatus enumeration, as follows:
Unknown - NeatUpload has not yet started receiving the upload
NormalInProgress - a normal (non-chunked) upload had started but not yet finished
ChunkedInProgress - an upload which uses chunked transfer coding has started but not yet finished.
Completed - the entire upload was successfully received
Cancelled - the user has cancelled the upload using the cancel link
Rejected - the upload has been rejected by the server because it is unacceptable for some reason
Failed - an unexpected error occurred while receiving the upload

If the WhenStatus property of a DetailsSpan and DetailsDiv control is set, the control will only render when the status is one of the space-separated list of statuses in the WhenStatus value.  If the WhenStatus property is not set, the control will always display.
BytesRead Number of bytes received by the server so far.  Pass this to FormatCount() to get a more readable value.
BytesTotal Total number of bytes in the upload.  Pass this to FormatCount() to get a more readable value.  For chunked uploads, BytesTotal is -1.
CountUnits The units associated with the result of a call to FormatCount().   If BytesTotal is greater that 1 million, returns the value of the MBUnits resource.  Otherwise, if BytesTotal is greater than 1 thousand, returns the value of the KBUnits resource.  Otherwise, returns the value of the ByteUnits resource.  Note: for chunked uploads, BytesRead is used instead of BytesTotal.
FormatCount(long count) Converts a number of bytes to units that would make BytesTotal readable.  If BytesTotal is greater that 1 million, count is formatted according to the MBCountFormat resource.  Otherwise, if BytesTotal greater than 1 thousand, count is formatted according to the KBCountFormat resource.  Otherwise, count is formatted according of the ByteCountFormat resource.  Note: for chunked uploads, BytesRead is used instead of BytesTotal.
BytesPerSec Rate (in bytes/sec) at which the server has received the upload.   While the upload is in progress, this is an average over the past 1-2 seconds.  When the upload has finished, this is an average over the entire upload.
FormatRate(int rate) Converts a rate to more readable units.  If rate is greater than 1 million, it is formatted according to the MBRateFormat resource.  Otherwise, if rate is greater than 1 thousand, it is formatted according to the KBRateFormat resource.  Otherwise, rate is formatted according of the ByteRateFormat resource.
FractionComplete A double in the range 0.0-1.0.  Computed as BytesRead/BytesTotal for normal (ie non-chunked) uploads.  Always 0.0 for chunked uploads.
TimeElapsed TimeSpan representing the time that has elapsed since the upload began.
TimeRemaining TimeSpan estimating the time left until the upload completes.  This is projected based on the elapsed time and the fraction complete.  Returns TimeSpan.MaxValue if BytesRead is 0 or if this is a chunked upload.
FormatTimeSpan(TimeSpan ts) Formats a TimeSpan for readability using the following code:
            string format;
            if (ts.TotalSeconds < 60)
                format = GetResourceString("SecondsFormat");
            else if (ts.TotalSeconds < 60*60)
                format = GetResourceString("MinutesFormat");
            else
                format = GetResourceString("HoursFormat");
            return String.Format(format,
                                      (int)Math.Floor(ts.TotalHours),
                                      (int)Math.Floor(ts.TotalMinutes),
                                      ts.Seconds,
                                      ts.TotalHours,
                                      ts.TotalMinutes);
Rejection An UploadException (eg UploadTooLargeException) when Status is Rejected.  Otherwise, null.  It can be useful to display Rejection.Message to the user when a rejection occurs.  NeatUpload throws an UploadTooLargeException if the total request size is more than the size specified with the maxRequestLength attribute of the <neatUpload> element.  The value of the UploadTooLargeException's Message property is based on the UploadTooLargeMessageFormat resource in Strings.resx.
Failure An Exception  when Status is Failed.  Otherwise, null.  It can be useful to display Failure.Message to the user when a failure occurs.
CurrentFileName The client-specified name of the file currently being received by the server.  
CancelVisible Whether the 'cancel' link should be visible.  This is only 'true' when an upload is in progress and NeatUpload can use JavaScript to cancel an upload.
StartRefreshVisible Whether the 'start refresh' link should be visible.  This is only 'true' when the progress display is not refreshing and NeatUpload can't use JavaScript to automatically start refreshing it when the upload starts.  
StopRefreshVisible Whether the 'stop refresh' link should be visible.  This is only 'true' when the upload is in progress, the progress display is refreshing, and NeatUpload can't use Javascript to cancel the upload.
CancelUrl, StartRefreshUrl. StopRefreshUrl The URLs for the 'cancel', 'start refresh', and 'stop refresh' links, respectively.

By default, ProgressPage calls it's GetResourceString() method to retrieve all of it's resources.  The default implementation uses the embedded resources compiled from Strings.resx.  If you want to use a different source, override GetResourceString() in your subclass.

For AJAX refreshless updates to work, your ProgressPage subclass must render as a well-formed XML document.  Here are a few tips to avoid the most common problems:

One final note: By default, DetailsDiv and DetailsSpan controls (like regular div and span controls) will not render as simple <div> and <span> elements in browsers that ASP.NET considers "downlevel".  Instead, div controls are rendered as tables and span controls may render with extra font tags, etc.  The Machine.config shipped with ASP.NET 1.1 contains a <browserCaps> section that causes all non-IE browsers to be considered downlevel, including Netscape, Firefox, Opera, Safari, etc.  To force the DetailsDiv and DetailsSpan controls to render as simple <div> and <span> elements even in supposedly downlevel browsers, set the control's UseHtml4 property to "true".  The default Progress.aspx does that for the DetailsDiv control with an id of "barDetailsDiv" that is used to draw the gray progress bar.  Note that even with the UseHtml4 property it is still a good idea to update or supplement the default ASP.NET 1.1 <browserCaps> section to make the rest of your web application render more cleanly in modern non-IE browsers.

Creating Custom Fallback Content for ProgressBar

If JavaScript is not available or the browser doesn't support IFRAMEs and the ProgressBar element is empty, a "Check Upload Progress" link will be displayed. To change the text of that link, simply place the desired text (or controls) between the begin and end tags of the ProgressBar element. For example:

	<Upload:ProgressBar id="progressBar" runat="server" >
<asp:Label id="label" runat="server" Text="Your Text Here"/>
</Upload:ProgressBar>

Creating a Custom UploadStorageProvider

By default, NeatUpload uses the built-in FilesystemUploadStorageProvider which streams uploaded files to a temporary directory until you move them.  If you would rather stream uploaded files to some other location (e.g. a database, or remote machine), or you want some other functionality not provided by FilesystemUploadStorageProvider, you need to create and use a custom UploadStorageProvider.  To create a custom UploadStorageProvider, create a class which inherits from UploadStorageProvider.  You will need to implement the following methods:

        public abstract void Initialize(string name, NameValueCollection attrs);

        public abstract UploadedFile CreateUploadedFile(UploadContext ctx,
                                                        string controlUniqueID,
                                                        string fileName,
                                                        string contentType);

When the configuration section where your provider is added is first relevant to a request, NeatUpload creates an instance of your provider and calls it's Initialize() method,  passing the value of the "name" attribute as the first parameter and the remaining attributes (other than "name", and "type") as the second parameter.  You can use the remaining attributes to pass provider-specific configuration information to your provider.

Whenever NeatUpload encounters a file upload associated with an InputFile control, it calls the CreateUploadedFile() method of the provider that is configured as the defaultProvider for the requested page.  When NeatUpload calls your provider's CreateUploadedFile() method, it passes the UploadContext associated with the current request, the ID of the InputFile control associated with the upload, along with browser-specified name and MIME type of the file.  CreateUploadedFile() is responsible for returning an instance of a class derived from UploadedFile.  NeatUpload will stream the file to that object, and delegate various InputFile members to the associated methods of your UploadedFile-derived object.

Your provider's CreateUploadedFile() can optionally use the ContentLength property of the passed UploadContext to get the length of the entire request, including all files.  That information could be used to reserve sufficient space to store the files, for example.  At the moment, no members of UploadContext other than ContentLength are available to providers.  Future releases of NeatUpload might use UploadContext to pass other similar information to providers.

Your provider can "reject" an upload at any time (eg based on MIME type, size, or content), by throwing an exception derived from UploadException.  When NeatUpload detects such an exception, it:
  1. updates the progress bar to indicate that the upload was rejected (i.e. sets Status to Rejected and Rejection to the UploadException that occured)
  2. if the progress bar is in a pop-up window, leaves the pop-up open so that the user can see the message
  3. asks the browser to stop the upload using javascript to simulate clicking the browser's Stop button
  4. waits 5 seconds to give the browser and opportunity to stop the upload
  5. rethrows the exception
NeatUpload has no way of knowing for sure whether the browser stopped the upload, so it always rethrows the exception.  If the browser did stop the upload, it will ignore any response that the application generates because of the error.  If the browser didn't stop the upload, it will display the response that the application generates.  You can customize that response by handling the Application.Error event or by using custom error pages.  UploadException is a subclass of HttpException, so you can control which custom error page is used by specifying the corresponding HTTP error code when you construct your UploadException.

When NeatUpload detects an exception that is not derived from UploadException, it assumes that it is an unexpected error, so it:
  1. updates the progress bar to indicate that an error occurred (i.e. sets Status to Failed and Failure to the Exception that occured)
  2. if the progress bar is in a pop-up window, leaves the pop-up open so that the user can see the message
  3. rethrows the exception
ASP.NET will handled the exception the same way any other exception is handled.  It will fire the Application.Error event and use custom error pages if they are configured.

For an example of how to implement a custom UploadStorageProvider, see FilesystemUploadStorageProvider.cs and FilesystemUploadedFile.cs.   For another example, see the files in the HashedInputFile directory.

Handling Non-absolute Paths in IE6

If instead of selecting a file to upload via the file selection dialog, an IE6 user types in a non-absolute path (e.g. just "x"), IE6 will not submit the form, no upload will occur, and NeatUpload will not start the progress display.  Unfortunately, this means that the user gets no feedback to indicate what the problem is.  If you want to provide feedback, NeatUpload provides a hook for that purpose.  When NeatUpload detects this situation, it checks to see if a JavaScript function named NeatUpload_HandleIE6InvalidPath() exists.   If it exists, NeatUpload calls that function, passing it the input file form element that contains the non-absolute path.  NeatUpload does not provide an implementation of that function by default, but you can provide your own implementation.  For example, your implementation might display "Invalid path" next to the input file form element so that the user knows what the problem is.

Since users almost always use the file selection dialog, this issue almost never arises.

Known Issues

NeatUpload does not work in IIS7's default application pool

Due to a bug in IIS7's Integrated Pipeline Mode, NeatUpload will not work in IIS7's default application pool. To workaround this, you need to configure your web site to use the "Classic .NET AppPool". To encourage Microsoft to fix the bug in a future IIS release, please give 5 stars to it. For more information on the issue, please see the discussion with IIS7 Program Manager Mike Volodarsky.

ASP.NET application-wide tracing disables NeatUpload

When ASP.NET tracing is enabled in the application Web.config, ASP.NET reads the entire upload before NeatUpload can access it. As a result, NeatUpload can't stream the upload to storage and can't provide a meaningful progress bar. Since NeatUpload can't do anything useful under such circumstances, it automatically disables itself. Specifically, it acts as though you set the useHttpModule attribute of the <neatUpload> element to "false". That prevents display of the progress bar and prevents streaming to storage, but your code should otherwise continue to function normally.

Note that this issue only arises when you enable application-wide ASP.NET tracing (i.e. via the Web.config). It does not arise if you only enable ASP.NET tracing at the page level using the Trace attribute of the <%@Page%> directive. Unfortunately, if you enable application-wide ASP.NET tracing but disable tracing for some or all pages using the Trace attribute of the <%@Page%> directive, the problem still occurs. This is because the Trace attribute of the <%@Page%> directive has no effect on whether ASP.NET reads in the entire request before NeatUpload can access it.

ProgressBar doesn't work with web gardens/farms

NeatUpload maintains information about the upload in the application state of the web application receiving the upload request. Since application state is not shared across instances of the web application, the ProgressBar will not be able to determine the state of the upload if the requests it makes go to a different instance of the web application. This can happen if you are running a web garden (i.e. multiple worker processes) or a web farm (i.e. multiple web servers). To ensure that you are not running a web garden, see Microsoft's documentation on Configuring Web Gardens with IIS 6. For high volume sites, you might be able to use a web farm if you are able to ensure that all requests from a particular client are routed to the same server (i.e. sticky IP).

Permissions on uploaded files depends on temporary directory

Uploaded files are created in a temporary directory and inherit the permissions of that directory. Calling InputFile.MoveTo() does not change the permissions. In some environments, files in the default temporary directory are not readable by web applications, and as a result uploaded files are not readable by the web application. To avoid this issue, simply change the temporary directory used by NeatUpload to a directory that has the permissions you desire.

Module conflicts can cause data length is shorter than Content-Length errors

When certain 3rd-party HttpModules are used along with NeatUpload a "Data length (xxx) is shorter than Content-Length (yyy) error" can occur. To avoid this place NeatUpload's UploadHttpModule first in the <httpModules> section, before all other modules. This problem has been reported with both PageBlaster and UrlMaster. It is often encountered on sites that installed NeatUpload as part of Ultra Video Gallery (UVG).

This issue is caused by the fact that NeatUpload (and any other module that monitors upload progress) needs to intercept the request before ASP.NET reads the request body. ASP.NET needs to read the request body in order to compute certain properties of the Request object (e.g. Request.Params). So if another module accesses one of those properties before NeatUpload intercepts the request, ASP.NET will read the request body and when NeatUpload tries to read the request body, there won't be anything there.

InvalidOperationException when using ProgressBar with EnableViewState="false"

In order to start the progress display only if client-side validation succeeds and the form is submitted to the server, NeatUpload needs to register an onsubmit statement that executes after other onsubmit statements such as those that do client-side validation. Most controls (e.g. validators) register their onsubmit statements during the PreRender event. To ensure that NeatUpload's onsubmit statement is executed later, it needs to be registered after PreRender handlers have been called, but before Render() is called. The PreRenderComplete event would be an good candidate, but unfortunately it is not available in .NET 1.1. Instead, NeatUpload registers its onsubmit statement in ProgressBar.SaveViewState(). Unfortunately, ASP.NET does not call SaveViewState() if EnableViewState is "false" for the page. To avoid silent failure when viewstate is disabled, ProgressBar.Render() throws an InvalidOperationException if the onsubmit statement was not registered.

If you see the InvalidOperationException error thrown from ProgressBar.Render(), the simplest workaround is to enable viewstate for the page containing the ProgressBar. However, if that is not desirable, you can explicitly call ProgressBar's RegisterOnSubmitStatement() method after other PreRender handlers. Under .NET 2.0 you can add it as a PreRenderComplete event handler like this:

PreRenderComplete += new System.EventHandler(progressBar.RegisterOnSubmitStatement);
Under .NET 1.1, you can override Page.OnPreRender() like this:
protected override void OnPreRender(EventArgs e)
{
    base.OnPreRender(e);
    progressBar.RegisterOnSubmitStatement(null, null);
}

HttpResponse.AppendToLog() does nothing when UploadHttpModule is used

Due to the design of NeatUpload this bug will not be fixed, however NeatUpload does provide a workaround.  Simply replace Response.AppendToLog() with Brettle.Web.NeatUpload.UploadHttpModule.AppendToLog().  That will work whether the UploadHttpModule is used or not.

Setting HttpResponse.HeaderEncoding does nothing when UploadHttpModule is used

Due to the design of NeatUpload this bug will not be fixed, however if the pages that need to set Response.HeaderEncoding are not pages to which the user will be uploading files, you can use location filtering to disable the UploadHttpModule for those pages. Also, if you are setting Response.HeaderEncoding so that you can specify a non-ASCII filename for a downloaded file via the filename parameter of the Content-Disposition header, you should consider other approaches. Even without the UploadHttpModule, setting Response.HeaderEncoding will only work if you set it to the encoding that the browser happens to be using. For example, setting it to "big5" will not cause the file to have a name with Chinese characters if the user is using the UTF-8 encoding. Also, if you set Response.HeaderEncoding to a value that is different from the encoding used by the browser, you could create a security hole.

There are two better approaches. For both approaches, the filename needs to be encoded using the HttpUtility.UrlPathEncode() method. That encodes non-ASCII characters using the %XX notation you often see in URLs. Once you have the UrlPathEncoded filename, you have 2 options.

Inline ProgressBars don't work with Opera

Opera does not seem to allow an IFRAME to be updated during form submission.  NeatUpload works around this by using a popup progress bar whenever the User-Agent header contains "Opera" (case-insensitive). Most browsers don't allow popups to be smaller that 500x100 pixels. If you specify a smaller width or height or use units other than pixels, NeatUpload will automatically use the minimum size in pixels.

Non-absolute path in IE7 causes the progress display to start even though no upload occurs

If instead of selecting a file to upload via the file selection dialog, the user types in a non-absolute path (e.g. just "x"), IE6 and IE7 will not submit the form and no upload will occur.  Under IE6, it is possible to detect this situation and prevent the progress display from starting.  The situation is detected by using Javascript to examine the value of the <input type="file"> form element.  If the value doesn't look like an absolute path, the progress display is not started.  In IE7, the value of the form element is always just the filename, not the full path, probably for security reasons.  As a result, NeatUpload can't prevent the progress display from starting because it's not possible to determine whether an absolute path was entered -- the value is "x" whether the user entered "x" or "c:\x".

Since users almost always use the file selection dialog, this issue almost never arises.

Troubleshooting

Handler returning false does not block default action in IE6

When not using NeatUpload, an event handler returning false will prevent the browser from performing the default action for the event. For example, consider a submit button with onclick="return confirm('Submit?');". If the user clicks the button a dialog will appear asking "Submit?". When not using NeatUpload, declining will prevent submission of the form. However, when using NeatUpload, the form will be submitted if the user is using IE6. To workaround this, change such handlers to set window.event.returnValue=false when they return false. For example, here is the implementation of a function that can be used instead of confirm() in the above example:

function confirmSubmit(msg)
{
    var confirmed = confirm(msg);
    if (!confirmed && window.event)
        window.event.returnValue = false;
    return confirmed;
}

This problem is caused by a difference in the order IE6 calls event handlers. In order to prevent non-triggers from starting an upload, NeatUpload registers event handlers on the <form> element for a variety of events (including onclick). Those handlers clear the filename if the event is not coming from a trigger. In standard-conformant browsers (i.e. modern browsers other than IE), it is possible to register those handlers so that they run before other handlers registered for controls within the form (e.g. submit buttons). As a result, the value returned by the submit button handler is what determines whether the form actually gets submitted. In IE, the NeatUpload form event handler runs last and the value it returns (always true) determines whether the form gets submitted. NeatUpload can't determine the value returned by the submit button handler and return it instead, so the above workaround is needed.

LinkButtons do not start the progress display in Internet Explorer for the Mac

NeatUpload overrides the form.submit() JavaScript method to start the progress display when the form is submitted programmatically (e.g. with a LinkButton). Mac IE does not support overriding form.submit() so NeatUpload can't start the progress display if a LinkButton is used to submit the form. The upload still occurs though. Since Mac IE is no longer supported by Microsoft and currently has less than 1% of the browser market, there are currently no plans to develop a workaround for this.

Sometimes responses larger than 1MB are not buffered

Without NeatUpload, ASP.NET (by default) buffers the entire response in memory before sending it. However, when NeatUpload's UploadHttpModule is installed, it will sometimes flush the buffer if the response is larger than 1 MByte. Responses smaller then 1 MBytes are not affected, but NeatUpload will sometimes flush larger responses even if the page does not contain any NeatUpload controls and does not receive uploads. To avoid this behavior you need to use location filtering to set <neatUpload ... useHttpModule="true".../> for the page. Very few applications need to buffer such large responses, but if your application needs such buffering, you can use location filtering to workaround the issue.

Technical note: The current behavior is a side-effect of a workaround for a problem where HttpResponse.TransmitFile() was causing the entire file to be buffered in memory when the UploadHttpModule was being used. Apparently, Microsoft coded TransmitFile() so that buffering is only avoided when the current HttpWorkerRequest is a particular subclass of HttpWorkerRequest. Since NeatUpload wraps the original HttpWorkerRequest in it's own subclass, ASP.NET does not see the original HttpWorkerRequest and does not bypass the buffering. Instead it calls HttpWorkerRequest.SendResponseFromFile(). NeatUpload's implementation of SendResponseFromFile() avoids the problem by flushing the response after every 1 MByte. Since this workaround affects SendResponseFromFile() but not other methods of sending response content, the flushing will not occur for all types of responses. That means you should not assume a response will be flushed simply because it is larger than 1 MByte.

Troubleshooting

If you think NeatUpload's UploadHttpModule might be causing a problem, the first thing you should do is set the <neatUpload> element's useHttpModule attribute to "false" (or remove the UploadHttpModule from the <httpModules> section) and try to reproduce the problem.  All of your existing code should continue to work - you just won't get progress bars and streaming of files to disk.  If the problem goes away when you remove the UploadHttpModule, it is a bug so please report it.  You can help me diagnosis the problem by doing the following:

  1. Set the debugDirectory attribute of the <neatUpload> element in your Web.config to the name of a directory in your webapp where NeatUpload can store copies of the request bodies it processes.  For example, <neatUpload ... debugDirectory="MyDebugDir" ...> would cause NeatUpload to store copies of the request bodies in the "MyDebugDir" directory under you application root.
  2. Reproduce the problem.
  3. Send me (dean at brettle dot com) a zip file containing the files from debug directory with the extensions ".body" and ".sizes".  Each ".body" file contains the body of a request you sent when reproducing the problem.  The corresponding ".sizes" file contains the MIME type of the request and the size of each chunk that NeatUpload read.  I can use the TestFilter.exe program to reproduce how the UploadHttpModule processed the request and (hopefully) determine exactly what went wrong.

 NeatUpload is capable of logging debug and error messages using log4net, but that capability is disabled by default to simplify installation and use of NeatUpload.  To enable logging with log4net:

  1. If your web application is not already using log4net:
    1. Download and build log4net.
    2. Add a reference to your web application that refers to the log4net.dll you built.
    3. Copy NeatUpload-1.1.x/log4net.config to the root directory of your web application.
    4. Add the following line to your Global.asax.cs before the namespace keyword:

      [assembly: log4net.Config.XmlConfigurator(ConfigFile="log4net.config", Watch=true)]

      or if you are using VB.NET, add the following to your Global.asax.vb:

      <assembly: log4net.Config.XmlConfigurator(ConfigFile:="log4net.config", Watch:=true)>
  2. Add a reference to the NeatUpload project that refers to log4net.dll.
  3. Change the properties/options for the NeatUpload project to define USE_LOG4NET.
  4. Rebuild NeatUpload.