NeatUpload Manual

Release Notes for NeatUpload-1.2.x

Upgrading from NeatUpload-1.1.x

NeatUpload-1.2.x is intended to be completely backward-compatible with NeatUpload-1.1.x.

To upgrade, simply replace your Brettle.Web.NeatUpload.dll with the copy from NeatUpload-1.2.x and copy Progress.js from NeatUpload-1.2.x/NeatUpload/ to the NeatUpload/ subfolder in your web application. 

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.2

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.

Uploading from a Client Application

If your users are uploading using their web browser, you don't need to read this section.  This section is for environments where a separate client application uses HTTP POST requests to uploads files to a form containing an InputFile control.  You should read the earlier sections before reading this one.

In order for NeatUpload to process requests from non-browser client applications, the query string of each request needs to contain a unique ID called a post-back ID.  NeatUpload looks for the post-back ID in a query string parameter.  The parameter name is configurable.  By default, NeatUpload looks for a parameter named "NeatUpload_PostBackID".  The post-back ID itself does not need to be in any particular format, but it needs to be unique for each request.  So, for example, if the client application uploads the file to :

http://www.tempuri.org/path/to/MyUploadPage.aspx?NeatUpload_PostBackID=afa02a6999e54541bb6873151d1dfbfc

then NeatUpload will use "afa02a6999e54541bb6873151d1dfbfc" as the post-back ID.

If NeatUpload finds the post-back ID in the query string it will assume that all files in the request are destined for InputFile controls and will stream them all to storage.  To ensure that you can access the uploaded files from your page, make sure that the name of the form field(s) used by the client application match the control IDs of your InputFile control(s).  So, if your client application uses a field name of "FILE001", you should use put the following in your page:

...
<Upload:InputFile id="FILE001" runat="server" />
...

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">
            <remove name="neatUpload" />
            <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"
            postBackIDQueryParam="parameter name, defaults to NeatUpload_PostBackID"
            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" />
        ...
        <httpModules>
            <add name="UploadHttpModule" type="Brettle.Web.NeatUpload.UploadHttpModule, Brettle.Web.NeatUpload" />
            ...
        </httpModules>
        ...
        <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.

Note that you still need to add the UploadHttpModule in the <httpModules> section so that it will be available to your application.  Location filtering just gives you finer control over which requests the UploadHttpModule actually touches.

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().

Changing the Query String Parameter Used When Uploading From Client Applications

If you are uploading from a client application other than a browser, you might not have control over the name of the query string parameter that the application uses to identify each upload.  To configure NeatUpload to look for a query string parameter with a particular name, the <neatUpload> element's postBackIDQueryParam attribute:
	<neatUpload ... postBackIDQueryParam="parameterName" ... />
The default value is "NeatUpload_PostBackID".

Configuring Individual InputFile Controls Programmatically

You can use the StorageConfig property of each InputFile control to change the way the UploadStorageProvider handles the file uploaded to that control.  The interpretation of InputFile.StorageConfig depends on which UploadStorageProvider is used.  The default FilesystemUploadStorageProvider only allows the temporary directory to be set via the StorageConfig property.  To programmatically set the temporary directory place a line like this in Page_Load():

inputFileId.StorageConfig["tempDirectory"] = "path_to_temp_directory";

Before using the StorageConfig property, it is critical to understand how the StorageConfig is communicated to the UploadStorageProvider to avoid some possible pitfalls.  When the InputFile control is rendered, NeatUpload encrypts the StorageConfig with an encryption key, signs it with a validation key and then renders the ciphertext and signature in a hidden form field.  When NeatUpload receives the upload request, it retrieves the form field value, verifies the signature, and decrypts the ciphertext to get the StorageConfig.  By default, NeatUpload automatically generates random encryption and validation keys when they are first needed during the application lifetime.  However, you can set them explicitly by setting the encryptionKey and validationKey attributes of the <neatUpload> element to random 32 and 40 digit hexadecimal strings, respectively.

Encrypting and signing the StorageConfig prevents an attacker from examining and modifying it.  However, it does not prevent replay attacks and it will cause uploads to be rejected if the server uses different keys for validation/decryption than it did for signing/encryption.  Follow the following rules to avoid these problems:

The SqlServerInputFile Extension

SqlServerInputFile is a NeatUpload extension generously contributed by Joakim Wennergren (jokedst at gmail dot com). It streams uploaded files to and from an SQL Server database and consists of the SqlServerInputFile web control and a custom UploadStorageProvider called SqlServerUploadStorageProvider.

Installation

To install SqlServerInputFile:
If you will be using the Visual Studio Web Form Designer, add the SqlServerInputFile control to your toolbox.  To do that, right-click on the Toolbox, click Add/Remove Items, Browse to  NeatUpload-1.2.x/Extensions/SqlServerInputFile/SqlServerUploader/bin/Hitone.Web.SqlServerUploader.dll, click Open, and then click OK.  A reference to Hitone.Web.SqlServerUploader.dll will automatically be added to your project the first time you use the designer to a SqlServerInputFile 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 Hitone.Web.SqlServerUploader.dll in the NeatUpload-1.2.x/Extensions/SqlServerInputFile/SqlServerUploader/bin/ directory.

Configuration

To configure your application to use the SqlServerUploadStorageProvider, add the following to your Web.config:

<configuration>
    <configSections>
        <sectionGroup name="system.web">
            <remove name="neatUpload" />
            <section name="neatUpload" type="Brettle.Web.NeatUpload.ConfigSectionHandler, Brettle.Web.NeatUpload" allowLocation="true" />
        </sectionGroup>
    </configSections>
    ...
    <system.web>
        ...
        <neatUpload ...
            defaultProvider="SqlServerUploadStorageProvider">
            <providers>
                <add name="
SqlServerUploadStorageProvider"
                 type="Hitone.Web.SqlServerUploader.SqlServerUploadStorageProvider, Hitone.Web.SqlServerUploader"
                 connectionString="connection string (required unless using connectionName to identify named connection in ASP.NET 2.0)"
                 options identifying stored procs or table/column names (required)
                 hashAlgorithm="optional name of hash algorithm supported by .NET (e.g. MD5, SHA1, etc), defaults to no hash"/>
            </providers>
        </neatUpload>
        ...
    </system.web>
    ...
</configuration>


The SqlServerUploadStorageProvider can either generate SQL itself or use stored procedures on the database server for all tasks. This is controlled by the attributes you specify when you add the provider to your Web.config. When the provider needs to do something, it first checks to see if you have specified a stored procedure to do it using one of the following attributes:

If you don't provide a stored procedure for what the provider needs to do, it will generate the SQL to do it. When generating SQL queries, the provider uses the values you specify with the following attributes:

To generate SQL queries, the provider only requires tableName and dataColumnName. The remaining attributes are optional.

Note: The generated SQL will not work with SQL Server 2000 because it requires the "$IDENTITY" object to reference the IDENTITY column of the table. An identityColumnName attribute might be added in a future release. Until then, SQL Server 2000 users will need to use stored procedures to workaround around this limitation.

Usage

To add the SqlServerInputFile to your forms:
  1. If you are using Visual Studio Web Form Designer, simply drag the SqlServerInputFile 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="SqlUpload" Namespace="Hitone.Web.SqlServerUploader" Assembly="Hitone.Web.SqlServerUploader"%>

    and add an SqlServerInputFile control to your aspx page wherever you want the user to choose a file, using something like this:
    <SqlUpload:SqlServerInputFile id="inputFileId" runat="server" />
  2. Set any properties that you would normally set on an InputFile or HtmlInputFile control.
  3. In your code-behind class, you can use the methods properties you would normally use on an InputFile object. In addition, SqlServerInputFile offers a Verify() method that tells the SqlServerUploadStorageProvider not to delete the uploaded file at the end of the request. Verify() is equivalent to calling inputFileId.MoveTo(inputFileId.FileName, MoveToOptions.Overwrite) but doesn't cause the extra database update. SqlServerInputFile also adds Hash, HashSize, and HashAlgorithm properties which return information about the cryptographic hash (if any) computed for the uploaded file.

Example

There is an example web application in Extensions/SqlServerInputFile/UploaderTest. To use it:

  1. Make Extensions/SqlServerInputFile/UploaderTest it's own web application.
  2. Create a database named FileStorageTest on your local machine, or change the connection strings in the Web.config to point to your database.
  3. Run the query for your version of SQL Server to create the table and stored procedures.

Example Setup Query for SQL Server 2005

CREATE TABLE [FileTable] (
	[Id] [int] IDENTITY (1, 1) PRIMARY KEY NOT NULL ,
	[FileName] [nvarchar] (50) NULL,
	[DataField] [image] NOT NULL ,
	[Partial] [tinyint] NOT NULL ,
	[MimeType] [nvarchar] (50) NULL ,
	[Created] [datetime] NOT NULL CONSTRAINT [DF_FileTable_Created] DEFAULT (getdate()),
	[FileHash] [nvarchar] (50) NULL ,
)
GO

Alter Procedure CreateBlob
	@Identity Numeric Output,
	@Pointer Binary(16) Output,
	@FileName VarChar(250) = null,
	@MIMEType VarChar(250) = null
As Begin Set NoCount ON;
	Insert Into FileTable (Datafield,FileName,MimeType,PartiallyUploaded) Values ('',@FileName,@MimeType,1)
	Select @Identity = SCOPE_IDENTITY()
	Select @Pointer = TEXTPTR(DataField) From FileTable Where $IDENTITY = @Identity
End

Go

Alter Procedure OpenBlob
	@Identity Numeric,
	@Pointer VarBinary(max) Output,
	@Size Int Output,
	@FileName VarChar(250) Output,
	@MIMEType VarChar(250) Output
As Begin Set NoCount On
	Select	@Pointer = TEXTPTR(DataField), 
			@Size = DATALENGTH(DataField),
			@FileName = [FileName],
			@MIMEType = MIMEType
	From FileTable Where $IDENTITY = @Identity
End

Go

Alter Procedure ReadBlob
	@Identity Numeric, --ignored in this implementation, here for reference
	@Pointer Binary(16),
	@Offset Int,
	@Size Int
As Begin Set NoCount On
	ReadText FileTable.DataField @Pointer @Offset @Size
End

Go

Alter Procedure WriteBlob
	@Identity Numeric, --ignored in this implementation, here for reference
	@Pointer Binary(16),
	@Bytes VarBinary(max),
	@Offset Int,
	@Delete Int
As Begin Set NoCount On
	UpdateText FileTable.DataField @Pointer @Offset @Delete With Log @Bytes
End

Go

Alter Procedure CleanUpBlob
	@Identity Numeric
As Begin Set NoCount On
	Update FileTable Set PartiallyUploaded=0 Where $Identity=@Identity
End

Go

Alter Procedure DeleteBlob
	@Identity Numeric
As Begin Set NoCount On
	Delete From FileTable Where $Identity=@Identity
End

Go

Alter Procedure RenameBlob
	@Identity Numeric,
	@FileName VarChar(250)
As Begin Set NoCount On
	Update FileTable Set [FileName]=@FileName Where $Identity=@Identity
End

Go

Alter Procedure FinalizeBlob
	@Identity Numeric,
	@Hash VarChar(250)
As Begin Set NoCount On
	Update FileTable Set FileHash=@Hash Where $Identity=@Identity
End

Example Setup Query for SQL Server 2000

The main difference between this version and the version for SQL Server 2005 is the lack of a $IDENTITY object and the cap of 8000 bytes in a varbinary

CREATE TABLE [FileTable] (
	[Id] [int] IDENTITY (1, 1) PRIMARY KEY NOT NULL ,
	[FileName] [nvarchar] (50) NOT NULL ,
	[DataField] [image] NOT NULL ,
	[Partial] [tinyint] NOT NULL ,
	[MimeType] [nvarchar] (50) NOT NULL ,
	[Created] [datetime] NOT NULL CONSTRAINT [DF_FileTable_Created] DEFAULT (getdate()),
	[FileHash] [nvarchar] (50) NULL ,
)

GO

CREATE Procedure CleanUpBlob
	@Identity Numeric
As Begin Set NoCount On
	Update FileTable Set Partial=0 Where Id=@Identity
End

GO

CREATE Procedure CreateBlob
	@Identity Numeric Output,
	@Pointer Binary(16) Output,
	@FileName VarChar(250) = null,
	@MIMEType VarChar(250) = null
As Begin Set NoCount ON;
	Insert Into FileTable (Datafield,FileName,MimeType,Partial) Values ('',@FileName,@MimeType,1)
	Select @Identity = SCOPE_IDENTITY()
	Select @Pointer = TEXTPTR(DataField) From FileTable Where id = @Identity
End

GO

CREATE Procedure DeleteBlob
	@Identity Numeric
As Begin Set NoCount On
	Delete From FileTable Where id=@Identity
End

GO

CREATE Procedure FinalizeBlob
	@Identity Numeric,
	@Hash VarChar(250)
As Begin Set NoCount On
	Update FileTable Set FileHash=@Hash Where id=@Identity
End

GO

CREATE Procedure OpenBlob
	@Identity Numeric,
	@Pointer VarBinary(8000) Output,
	@Size Int Output,
	@FileName VarChar(250) Output,
	@MIMEType VarChar(250) Output
As Begin Set NoCount On
	Select	@Pointer = TEXTPTR(DataField), 
			@Size = DATALENGTH(DataField),
			@FileName = [FileName],
			@MIMEType = MIMEType
	From FileTable Where id = @Identity
End

GO

CREATE Procedure ReadBlob
	@Identity Numeric, --ignored in this implementation, here for reference
	@Pointer Binary(16),
	@Offset Int,
	@Size Int
As Begin Set NoCount On
	ReadText FileTable.DataField @Pointer @Offset @Size
End

GO

CREATE Procedure RenameBlob
	@Identity Numeric,
	@FileName VarChar(250)
As Begin Set NoCount On
	Update FileTable Set [FileName]=@FileName Where id=@Identity
End

GO

CREATE Procedure WriteBlob
	@Identity Numeric, --ignored in this implementation, here for reference
	@Pointer Binary(16),
	@Bytes VarBinary(8000),
	@Offset Int,
	@Delete Int
As Begin Set NoCount On
	UpdateText FileTable.DataField @Pointer @Offset @Delete With Log @Bytes
End

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">
                <remove name="neatUpload" />
                <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 virtual  UploadedFile CreateUploadedFile(UploadContext ctx,
                                                        string controlUniqueID,
                                                        string fileName,
                                                        string contentType,
                                                        UploadStorageConfig storageConfig);

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 second 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, the browser-specified name and MIME type of the file, and the value of the InputFile control's StorageConfig property.  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.

If the NameValueCollection provided by UploadStorageConfig is not sufficient for your provider, you can override UploadStorageProvider.CreateUploadStorageConfig() to return your own UploadStorageConfig subclass.  Your subclass can override the UploadStorageConfig.Serialize(Stream) and UploadStorageConfig.Deserialize(Stream) methods to serialize itself to the provided Stream and deserialize itself from the provided Stream.  The base class implementations of these methods use LosFormatter to provide a concise serialization of the NameValueCollection as a Hashtable.

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

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).

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.

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.

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.

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.