NeatUpload™ Manual

by Dean Brettle
Last updated 01/31/09 02:14:23AM

Table of Contents

1 Introduction

1.1 Requirements

1.2 Branding, Licensing, and the Trademark

1.3 Version Numbering

2 Release Notes for NeatUpload-1.3.x

2.1 Upgrading from NeatUpload-1.2.x and NeatUpload-1.1.x

2.2 Upgrading from NeatUpload-1.0.x

2.3 New Features in NeatUpload-1.3

3 Usage

3.1 Installing NeatUpload

3.2 Using NeatUpload with the Visual Studio Web Form Designer

3.3 Using NeatUpload without the Visual Studio Web Form Designer

3.4 Allowing Users with Flash to Select Multiple Files from One File Selection Dialog

3.5 Validating File Names

3.6 Preventing Accidental Upload Interruptions

3.7 Avoiding Unnecessary Uploads and Progress Displays

3.8 Showing Processing Progress

3.9 Uploading without InputFile

3.10 Uploading from a Client Application

4 Configuration

4.1 Using the <neatUpload> Configuration Section

4.2 Changing the Temporary Directory Used by the FilesystemUploadStorageProvider

4.3 Supporting Web Gardens and Web Farms

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

4.5 Limiting the Size of Upload Requests

4.6 Changing the Maximum Size of Normal Requests

4.7 Limiting the Upload Rate

4.8 Configuring Individual InputFile and MultiFile Controls Programmatically

4.9 Reducing Server Load

4.10 Changing the Location of NeatUpload's Files

4.11 Changing the Query String Parameter Used When Uploading From Client Applications

4.12 Changing the Encryption and Validation Algorithms Used

5 Customization

5.1 Customizing Progress.aspx

5.2 Creating Custom Fallback Content for ProgressBar

5.3 Advanced Progress Bar Customization

5.4 Customizing the MultiFile Button

5.5 Customizing MultiFile's Handling of Queued Files

5.6 Creating a Custom UploadStorageProvider

5.7 Creating a Custom UploadStateStoreProvider

5.8 Handling Non-absolute Paths in IE6

6 Extensions

6.1 The SqlServerInputFile Extension

6.1.1 Installation

6.1.2 Configuration

6.1.3 Usage

6.1.4 Example

6.1.4.1 Example Setup Query for SQL Server 2005

6.1.4.2 Example Setup Query for SQL Server 2000

6.2 The GreyBoxProgressBar Extension

6.3 The HashedInputFile Extension

7 Known Issues

7.1 ASP.NET application-wide tracing disables NeatUpload

7.2 Permissions on uploaded files depend on temporary directory

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

7.4 HttpResponse.AppendToLog() does nothing when UploadHttpModule is used

7.5 Setting HttpResponse.HeaderEncoding does nothing when UploadHttpModule is used

7.6 Inline ProgressBars don't work with Opera

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

7.8 Handler returning false does not block default action in IE6

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

7.10 Sometimes responses larger than 1MB are not buffered

7.11 NeatUpload controls might not work properly within an UpdatePanel

7.12 NeatUpload might fail when using Windows Authentication

7.13 NeatUpload might fail when using SSL/HTTPS

7.14 Application restarts can interrupt uploads or blank ProgressBars

8 Troubleshooting



1 Introduction

The NeatUpload™ ASP.NET component allows developers to stream uploaded files to storage (e.g. disk or a database) 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 seven custom controls (InputFile, MultiFile, ProgressBar, UnloadConfirmer, HiddenPostBackID, 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. MultiFile is a custom control similar to InputFile but with support for uploading multiple files and for specifying the look of the control.

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.

UnloadConfirmer is a custom control that displays a confirmation dialog if the user tries to do something that would interrupt an upload.

HiddenPostBackID is a custom control that tells NeatUpload to stream to storage all files uploaded from a page, including those uploaded with standard ASP.NET upload controls.

UploadHttpModule is an HttpModule that intercepts HTTP requests, streams InputFile/MultiFile 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 NeatUpload controls. For this reason, you can configure NeatUpload to only use UploadHttpModule for certain pages (or even none) while continuing to use InputFile , MultiFile , 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.

1.1 Requirements

To use NeatUpload you will need:

1.2 Branding, Licensing, and the Trademark

Some NeatUpload controls display a "Powered by NeatUpload" branding unless you are using a version that someone has modified to remove the branding. This branding is designed to encourage you to contribute to NeatUpload in one of the following ways:

Both the branded and unbranded versions are licensed under the GNU Lesser General Public License (LGPL). The only difference is whether the branding is present. That means you can legally redistribute (or fork) the unbranded version in accordance with the LGPL. Note however that "NeatUpload" is a trademark for the combination of the code I distribute and my support of it. As such, you are not allowed to promote your redistribution of the unbranded version (or a derivative of either version) in a way that could imply my endorsement without my permission. For example, don't call it "Unbranded NeatUpload" or say that it is "based on NeatUpload", because I don't want to get support requests from folks using it.

To summarize, if you are using a branded version of NeatUpload you have several options for removing the branding. In addition, you can use NeatUpload, modify it (including removal of the branding), and redistribute it in accordance with the terms of LGPL license. This includes using it to create products/services that compete with NeatUpload. However, you can't promote your redistribution of an unbranded version of NeatUpload in a way that could imply my endorsement because doing so dilutes the NeatUpload trademark.

I view branding NeatUpload as an experiment, and I'm very interested in feedback, both positive and negative. Please post your comments, or email me directly.

1.3 Version Numbering

NeatUpload release names have the form NeatUpload-major.minor.patch. When comparing versions, the same major and minor numbers but a higher patch number (e.g. 1.1.4 vs. 1.1.3) means the new version contains only fixes for bugs in existing assemblies or new features implemented in new optional assemblies. There is very little risk (and sometimes significant reward) in installing the new version. The same major number but a higher minor number (e.g. 1.2.patch vs. 1.1.patch) means the new version adds new features to existing assemblies but any backward incompatibilities are extremely minor. You should not need to modify your application to continue to use the old features, but you might need to make modification to use the new features. However, you should review the release notes and test your application after upgrading to ensure that bugs weren't inadvertently introduced. A higher major number (e.g. 2.minor.patch vs. 1.minor.patch) means the new version contains significant changes that are not backward-compatible. Depending on what features your application uses, you might need to modify it to work with the new version. Review the release notes for details.

2 Release Notes for NeatUpload-1.3.x

2.1 Upgrading from NeatUpload-1.2.x and NeatUpload-1.1.x

NeatUpload-1.3.x is intended to be almost completely backward-compatible with NeatUpload-1.2.x and NeatUpload-1.1.x. See below for compatibility exceptions. To upgrade, simply replace your Brettle.Web.NeatUpload.dll with the copy from NeatUpload-1.3.x/dotnet/app/bin/ and copy everything from the NeatUpload-1.3.x/dotnet/app/NeatUpload/ folder into the NeatUpload folder that is a child of your application's root directory. You also need to disable Windows Authentication and enable Anonymous Authentication for the NeatUpload folder.

If you have customized your Progress.aspx page, it will probably work unchanged under NeatUpload-1.3.x but merging your changes into the latest version should not be difficult and is recommended.

If you are using the <neatUpload> configuration section, consider moving it out of the <system.web> section and adding the xmlns="http://www.brettle.com/neatupload/config/2008" attribute as described in Using the <neatUpload> Configuration Section. The old location under <system.web> is still supported but the documentation uses the new location and adding the xmlns attribute will provide Intellisense support in Visual Studio.

The only compatiblity exceptions in NeatUpload-1.3 are:

2.2 Upgrading from NeatUpload-1.0.x

NeatUpload-1.3.x is intended to be almost completely backward-compatible with NeatUpload-1.0.x.  The only exception other than those mentioned above 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.3.x/dotnet/app/bin/ and copy everything from the NeatUpload-1.3.x/dotnet/app/NeatUpload/ folder into the NeatUpload folder in your application. If you have not customized your Progress.aspx page, you should replace it with NeatUpload-1.3.x/dotnet/app/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.3.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.3.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.

2.3 New Features in NeatUpload-1.3

3 Usage

The following instructions should work with Visual Studio 2003 and Visual Studio 2005.  If you are using Visual Studio 2002, you might be able to get NeatUpload to work by following the instructions in this forum thread .

3.1 Installing NeatUpload

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- version, where version is the version of the release you are installing.

  2. If your application has "Full" trust, you can safely skip this step. If your application does not have "Full" trust, a server administrator (e.g. your hosting provider) needs to install NeatUpload in the GAC. This is necessary because NeatUpload (and all other similar components) needs to access the underlying HttpWorkerRequest object. To install NeatUpload in the GAC, the server administrator should:

    1. Use Windows Explorer to open %WINDIR%/assembly. The window will list the assemblies currently in the GAC along with their version number, etc.

    2. In another Windows Explorer window, open the NeatUpload- version /dotnet/app/bin folder.

    3. Drag and drop Brettle.Web.NeatUpload.dll and (if present) Policy. major.minor .Brettle.Web.NeatUpload.dll from the NeatUpload- version /dotnet/app/bin folder into the GAC window to add them to the GAC.

    4. Find the Brettle.Web.NeatUpload assembly that was just added and note the value in the "Version" column.

    5. Tell NeatUpload users that the full strong name for the Brettle.Web.NeatUpload assembly is Brettle.Web.NeatUpload, Version= value in version column , Culture=neutral, PublicKeyToken=C95290D92C5893C8".

    To use NeatUpload after it has been installed in the GAC, you need to: Use the full strong name wherever the assembly is referenced in your application's Web.config. For example, if this documentation says to use:

      type="Brettle.Web.NeatUpload.UploadHttpModule,Brettle.Web.NeatUpload"

    you would use:

      type="Brettle.Web.NeatUpload.UploadHttpModule,Brettle.Web.NeatUpload,Version=valuefromadmin,Culture=neutral,PublicKeyToken=C95290D92C5893C8" 

    The server administrator will provide the "Version" portion of the strong name.

  1. Either create app_data/NeatUpload_Temp under your application root and make it writable by the account that your application is running as, or change the temporary directory used by the default provider.

  2. (Optional) To see the demo running on your server, configure IIS, mod_mono, or XSP so that NeatUpload- version /dotnet/app/ is a web application. Then point your browser at the Brettle.Web.NeatUpload/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- version /dotnet/app/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. If you want to use any of the NeatUpload extensions, add the other corresponding assemblies to your toolbox.

  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 (and any extension assemblies of interest) in the NeatUpload- version /dotet/app/bin directory.

  5. In your web application's root directory, create a NeatUpload/ child directory by copying NeatUpload- version /dotnet/app/NeatUpload/ and its contents.  That subdirectory contains various files that NeatUpload needs in order to function properly. Disable Windows Authentication and enable Anonymous Authentication for the NeatUpload folder.

  6. Add the following line to your Web.config at the top of the configuration/system.web/httpModules section:

    <add name="UploadHttpModule" type="Brettle.Web.NeatUpload.UploadHttpModule, Brettle.Web.NeatUpload" />
  7. Add the following line to your Web.config at the top of the configuration/system.webServer/modules section to support IIS7's Integrated Pipeline Mode (which is used in it's default application pool):

    <add name="UploadHttpModule" type="Brettle.Web.NeatUpload.UploadHttpModule, Brettle.Web.NeatUpload" preCondition="managedHandler"/>
  8. (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.

  9. (Optional) Copy the NeatUpload- version /dotnet/app/Brettle.Web.NeatUpload/ folder into your application. Point your browser at the Demo.aspx in that folder and verify that the demo functions properly.

3.2 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: MultiFile, 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(s).  If you are using the InputFile control, 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);

If you are using the MultiFile control, the multiFileId .Files property returns an array of UploadedFile objects. Those objects have members similar to the above InputFile members, that you can use to save and access the uploaded files.

3.3 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 or MultiFile control to your aspx page wherever you want the user to choose a file, using something like this:

    <Upload:InputFile id="inputFileId" runat="server" />

    or this:

    <Upload:MultiFile id="multiFileId" 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 MultiFile, 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 MultiFile multiFileId;
    protected ProgressBar progressBarId;
    protected Button submitButtonId;
  6. In your codebehind file, process the uploaded file.  If you are using the InputFile control, 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);

If you are using the MultiFile control, the multiFileId .Files property returns an array of UploadedFile objects. Those objects have members similar to the above InputFile members, that you can use to save and access the uploaded files.

3.4 Allowing Users with Flash to Select Multiple Files from One File Selection Dialog

By default, the NeatUpload MultiFile control requires the user to use select each file they want to upload using a new file selection dialog. This can be cumbersome when uploading a large number of files. If you set MultiFile's UseFlashIfAvailable property to true, then users with Flash 8 or above and a compatible browser will be able select multiple files from a single file selection dialog (e.g. using shift-click or ctrl-click). This functionality has been tested under IE7, Windows Firefox 3, and Windows Safari 3 but should work with any browser that supports using JavaScript to make calls to ActionScript functions defined in transparent Flash movies. Users of browsers that don't have the required support (e.g. Opera 9 and Linux Firefox 2) can continue to select one file per-dialog. You can set MultiFile's FlashFilterExtensions property to a semicolon delimited list of extension patterns (e.g. “*.jpg;*.gif”) that Flash will allow the user to select in the file selection dialog. You can also set MultiFile's FlashFilterDescription to a short textual description of the files that Flash will allow the user to select. Flash displays that description in the file selection dialog.

3.5 Validating File Names

NeatUpload's MultiFile and InputFile controls can be used with validation controls (e.g. RegularExpressionValidator) to restrict the file names that are allowed. If the validation control is capable of client-side validation and the browser supports JavaScript, the file names will be validated before the upload starts. Otherwise, they will be validated after the files have been uploaded. Note that the validation controls in ASP.NET 1.1 don't do client-side validation for many non-IE browsers. That is fixed in ASP.NET 2.0 and later. Developers using ASP.NET 1.1 should consider alternative validation controls.

To validate an InputFile or MultiFile control using an ASP.NET validator, just set the validator's ControlToValidate property to the ID of the InputFile or MultiFile control. For the InputFile control, the validator will validate the ValidationFileName property which is the same as the FileName property except that is an empty string (instead of null) when there are no files. For the MultiFile control, the validator will validate the ValidationFileNames property which is a semicolon-delimited list of the file names, or an empty string if there are no files. The NeatUpload- version /dotnet/app/Brettle.Web.NeatUpload/Demo.aspx page contains examples of using RegularExpressionValidators to validate InputFile and MultiFile controls.

3.6 Preventing Accidental Upload Interruptions

By default, any in-progress uploads are uploaded when a user resubmits the form, closes the browser window, or attempts to view another page in the same window that started the upload. To prevent a user from accidentally interrupting the upload in any of those ways add the UnloadConfirmer control to your page. It will display a confirmation dialog to the user if they do something that would interrupt the upload. The text of the confirmation defaults to the UnloadConfirmation resource in NeatUpload  version /dotnet/src/Brettle.Web.NeatUpload/NeatUpload.Strings.resx and can be overriden using the Text property of the UnloadConfirmer control.

3.7 Avoiding Unnecessary Uploads and Progress Displays

By default, anytime the user submits a form containing a non-empty MultiFile or InputFile and a ProgressBar, the progress display is started (either inline or in a popup).  If all MultiFile and 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 clear all file upload controls (including MultiFile, InputFile, and ASP.NET's FileUpload and HtmlInputFile) 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 NeatUpload  version /dotnet/src/Brettle.Web.NeatUpload/NeatUpload.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. The first way is to include the ID of the control in the ProgressBar's Triggers property/attribute. Multiple IDs must be space-separated. Each ID can either by the ID or ClientID property of the trigger. The second way is to 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.

3.8 Showing Processing Progress

By default, the NeatUpload ProgressBar displays "Processing..." (the ProcessingMessage resource in NeatUpload  version /dotnet/src/Brettle.Web.NeatUpload/NeatUpload.Strings.resx) if it refreshes while the code associated with your page runs. In most cases your code finishes running before the display refreshes, so you never see that text. However, if you perform some long-running task (e.g. converting large images to a different format) you can use the ProgressBar to display how that task is progressing.

To display the progress of a long-running task, you must first:

  1. Use EnableSessionState="false" in your <%@Page ...%> directive. If you don't do that your page will hold a lock on the session for the duration of your long-running task. That will block the display of any other pages which need access to the session until your long-running task completes. More importantly, NeatUpload's progress display will not update because it needs access to the session to determine how the upload is progressing. If you need to access the session from your page, you should consider moving the code that requires that access into a web method that you can can from your page.

  2. By setting EnableSessionState="false" you also prevent ASP.NET from starting a new session if the user doesn't yet have one. If the user doesn't have a session when the upload starts and the UploadStateStoreProvider you are using requires a session, then the progress display will not update at all. This happens most often during development when you go directly to the page with EnableSessionState="false" to test it. To avoid this issue, ensure that the user visits a page without EnableSessionState="false" that stores something in the session, before visiting the page with EnableSessionState="false". The page must store something in the session, because that is the only way to guarantee that a new session will be established. If the user isn't guaranteed to go through such a page first, you can create a redirector page that stores something in the session and then uses Response.Redirect() to redirect the user to the target page. Alternatively, if you aren't using a web garden or web farm, you can avoid the need to establish a session by configuring NeatUpload to use the InProcUploadStateStoreProvider as described in Reducing Server Load.

  3. If you're form doesn't contain any MultiFile or InputFile controls you need to add a HiddenPostBackID control at the top of the form so that NeatUpload can associate the form submission with the progress display.

  4. If you want to display progress even if the user doesn't upload a file (either because your form doesn't have a way to select file or because the user chose not to select a file), you need to set the ProgressBar's AutoStartCondition property to the string "true".

To actually update the progress display from the your page:

  1. Set the ProgressBar's ProcessingProgress property to a new ProgressInfo object, passing to the constructor the maximum number of work units that will be performed and a string describing those units.

  2. Update the ProgressInfo's Value property as the task progresses. The Value property will control the position of the bar. If Value is 0, no bar will be shown. If it is equal to the Maximum property (initialized via the constructor), a full bar will be shown.

  3. Optionally, update the ProgressInfo's Text property. By default, when a ProgressBar's ProcessingProgress property has been set, it will display "Value/Maximum Units processed" where Value, Maximum, and Units are the corresponding properties of the ProgressInfo object. You can localize that text via the ProgressInfoFormat resource in NeatUpload  version /dotnet/src/Brettle.Web.NeatUpload/NeatUpload.Strings.resx or you can replace that text completely by setting the ProgressInfo's Text property.

Here's an example:

...
myProgressBar.ProcessingProgress = new ProgressInfo(5, "images");
for (int i = 1; i <= 5; i++)
{
ConvertImage(i);
myProgressBar.ProgressInfo.Value = i;
}
myProgressBar.ProgressInfo.Text = "Image conversion complete";
...

For a complete working example, see Processing.aspx and Processing.aspx.cs in the NeatUpload-1.3.x/dotnet/app/Brettle.Web.NeatUpload/tests/ directory.

3.9 Uploading without InputFile

By default, NeatUpload will stream to storage only those files associated with MultiFile or InputFile controls.  Other files will be processed by ASP.NET and will only cause any ProgressBars to update if they happen to occur after a file that NeatUpload has streamed.  To tell NeatUpload to stream the other files, you must put a HiddenPostBackID control in your form before the controls used to upload those other files.  If you are using the Visual Studio web form designer, just drag-and-drop the control at the top of your form.  Otherwise, just manually add <Upload:HiddenPostBackID runat="server"/> just inside the <form> element on your page.  There are no properties to set.

Files that NeatUpload streams to storage are not available via the standard ASP.NET Request.Files property.  Instead, they must be accessed via the static Brettle.Web.NeatUpload.UploadModule.Files property.  Since ASP.NET doesn't allow subclassing of the HttpFileCollection returned by Request.Files (nor the HttpPostedFile class), NeatUpload attempts to mimic their API with new classes called UploadedFileCollection and UploadedFileUploadModule.Files only contains an UploadedFile object for files that NeatUpload streamed to storage. To convert an HttpPostedFile to an UploadedFile, pass it to UploadModule.ConvertToUploadedFile(). If the UploadHttpModule is listed in the <httpModules> section (even if it is disabled), ConvertToUploadedFile() will stream the file to storage and return a corresponding UploadedFile.  If no upload module is listed in the <httpModules> section, ConvertToUploadedFile() will return a subclass of UploadedFile that delegates to the underlying HttpPostedFile.

There is one important difference between UploadedFile and HttpPostedFile. You must call the Dispose() method of each UploadedFile you use when you are done with it. If you do not call Dispose(), temporary files or other similar resources might not get cleaned up. This is particularly important if the UploadHttpModule is disabled or not listed in the <httpModules> section.

3.10 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 your application.  You should read the earlier sections before reading this one.

In order for NeatUpload to process requests from non-browser client applications, each request needs to contain a unique ID called a post-back ID.  NeatUpload looks for the post-back ID in a parameter that is in either the query string or in a form field in the body of the request that occurs before the files to be streamed.  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 stream all files in the request to storage.  You can access the uploaded file via the static Brettle.Web.NeatUpload.UploadModule.Files property. Alternatively, you can put MultiFile or InputFile controls on your page and make sure that the name of the form field(s) used by the client application match the control IDs of your MultiFile or InputFile control(s).  So, if your client application uses a field name of "FILE001", you could put the following in your page:

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



4 Configuration

4.1 Using the <neatUpload> Configuration Section

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

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

In addition, if you are using ASP.NET 2.0 or higher under medium trust, you need to add requirePermission="false" to the <section> element above.

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

<configuration>
...
<neatUpload xmlns="http://www.brettle.com/neatupload/config/2008"
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"
maxUploadRate="rate in KBytes/sec, defaults to -1 which means unlimited"
postBackIDQueryParam="parameter name, defaults to NeatUpload_PostBackID"
multiRequestUploadHandlerUrl=" URL that handles the requests in a multi-request upload,
defaults to ~/NeatUpload/MultiRequestUploadHandler.ashx
"
debugDirectory ="d irectory to which debug info should be written, defaults to none "
decryption =" name of the SymmetricAlgorithm to use to encrypt/decrypt protected data,
defaults to .NET default algorithm used by SymmetricAlgorithm.Create()
"
validation =" name of the HashAlgorithm to use to validate protected data,
defaults to .NET default algorithm used by HashAlgorithm.Create()
"
decryptionKey ="32 hex-digit key used to encrypt/decrypt protected data,
defaults to auto-generated for each app instance
"
stateMergeIntervalSeconds ="secs between merges of the current and stored upload states,
defaults to 1
"
stateStaleAfterSeconds =" secs before an unchanged upload state can be removed from the store,
defaults to 60
"
defaultStorageProvider="friendly name,
defaults to a FilesystemUploadStorageProvider with default temp dir
"
defaultStateStoreProvider="friendly name, defaults to a AdaptiveUploadStateStoreProvider"
>
<providers>
<add name="friendly name"
type="type derived from Brettle.Web.NeatUpload.UploadStorageProvider"
provider-specific- attributes ... />
</providers>
</neatUpload>
...
</configuration>

All of the attributes are optional. The xmlns attribute allows the Visual Studio XML Editor to provide Intellisense support for the <neatUpload> element using the NeatUpload/NeatUploadConfig.xsd file in your application. The remaining attributes are described in the remainder of this section and in the sections linked to the attribute names above.

The <providers> element is also optional. You only need to use it if you are using an UploadStorageProvider or UploadStateStoreProvider other than the default providers, or if 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 defaultStorageProvider or defaultStateStoreProvider 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.

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 "defaultStorageProvider" or “defaultStateStoreProvider” for the requested page is actually used for that request. The others might be configured as default providers for other pages.

4.2 Changing the Temporary Directory Used by the FilesystemUploadStorageProvider

By default, NeatUpload puts uploaded files in temporary files in YOUR_APP_ROOT /app_data/NeatUpload_Temp/ if it exists (or can be created) and is writable by your application, or failing that, it puts them in the system's temporary directory. You can specify a different directory using the tempDirectory attribute of the FilesystemUploadStorageProvider, like this:

  <neatUpload ... defaultStorageProvider="FilesystemUploadStorageProvider" ...>
<providers>
...
<add name="FilesystemUploadStorageProvider"
type="Brettle.Web.NeatUpload.FilesystemUploadStorageProvider"
tempDirectory="
path, defaults as described above " ... />
...
</providers>
</neatUpload>

If you specify a relative path, it will be relative to your application root directory.

4.3 Supporting Web Gardens and Web Farms

By default, NeatUpload won't work properly on a web garden or web farm. To use NeatUpload in a web garden or web farm, you need to specify the same random 32-hex-digit decryptionKey attribute in the <neatUpload> section of each server's Web.config. That key allows NeatUpload to securely communicate the state of uploads across all application instances.

In addition, if you are using a control that performs multi-request uploads like the MultiFile control with useFlashWhenAvailable=”true” you must specify a shared location for the uploaded files to be stored temporarily. You could configure a FilesystemUploadStorageProvider to use a network share as the temporary directory, if the network share is readable and writable by the ASP.NET worker process. Alternatively, you could use a different UploadStorageProvider (e.g. the one used by the SqlServerInputFile extension) which stores the uploaded files in a shared location.

4.4 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.  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>
...
</system.web>
...
<neatUpload ... useHttpModule="false" maxNormalRequestLength="100" maxRequestLength="2097151">
<providers>
<add name="special" type="Brettle.Web.NeatUpload.FilesystemUploadStorageProvider"
tempDirectory="SpecialTempDirectory" />
</providers>
</neatUpload>
...
<location path="Upload.aspx">
<system.web>
<httpRuntime maxRequestLength="2097151" executionTimeout="3600" />
</system.web>
<neatUpload useHttpModule="true" />
</location>
<location path="Special.aspx">
<system.web>
<httpRuntime maxRequestLength="2097151" executionTimeout="3600" />
</system.web>
<neatUpload useHttpModule="true" defaultStorageProvider="special" />
</location>
...
</configuration>

That configuration will cause the UploadHttpModule to only be used for requests to Upload.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 Upload.aspx use the default provider and the default 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.

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

4.6 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 differences are:

  1. For upload requests where the non-file portion exceeds the maxNormalRequestLength limit, NeatUpload throws a NonfilePortionTooLargeException instead of an UploadTooLargeException.

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

4.7 Limiting the Upload Rate

By default, NeatUpload receives the upload as fast as it can.  To limit the rate at which the upload is received, use the <neatUpload> element's maxUploadRate attribute:

<neatUpload ... maxUploadRate="rateInKBytesPerSec" ... />

This is mostly useful when testing on your local machine.  By limiting the upload rate, you can get a better idea how the progress display will look when users upload over the internet.

4.8 Configuring Individual InputFile and MultiFile Controls Programmatically

You can use the StorageConfig property of each MultiFile and InputFile control to change the way the UploadStorageProvider handles the file uploaded to that control.  The interpretation of the StorageConfig property 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 MultiFile or InputFile control is rendered, NeatUpload encrypts the StorageConfig with a key then renders the ciphertext and signature in a hidden form field.  When NeatUpload receives the upload request, it retrieves the form field value, decrypts the ciphertext to get the StorageConfig.  By default, NeatUpload automatically generates a random key when it is first needed during the application lifetime.  However, you can set it explicitly by setting the decryptionKey attributes of the <neatUpload> element to a random 32 digit hexadecimal string.

Encrypting 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 receiving the upload uses a different key than the server that rendered the upload page.  Follow the following rules to avoid these problems:

4.9 Reducing Server Load

NeatUpload provides several configuration options that can help you reduce the load on the server associated with using NeatUpload.

First, if you are not using a web garden or web farm but have a session state mode other than InProc or Off, the default AdaptiveUploadStateStoreProvider will act like the SessionBasedUploadStateStoreProvider which will make extra web requests to transfer upload state to and from the user's session. To avoid those requests, you can use the InProcUploadStateStoreProvider instead of the default AdaptiveUploadStateStoreProvider, like this:

  <neatUpload ... defaultStateStoreProvider="InProcUploadStateStoreProvider" ...>
<providers>
...
<add name="InProcUploadStateStoreProvider"
type="Brettle.Web.NeatUpload.InProcUploadStateStoreProvider,
Brettle.Web.NeatUpload"/>
...
</providers>
</neatUpload>

However, note that using the InProcUploadStateStoreProvider will cause progress displays to go blank and Flash uploads to stop whenever the application is restarted.

Next, if you are using a web garden or web farm, you can reduce the rate at which those extra web requests are made my setting the value of the <neatUpload> element's stateMergeIntervalSeconds attribute to a value larger than the default value of 1. However, note that making such a change will cause the progress display to appear to update at no more than the corresponding rate.

Lastly, if you find that users are interrupting many uploads and the partial uploads are occupying too much storage space, you can reduce the time that NeatUpload waits to remove such partial uploads by reducing the value of the <neatUpload> element's stateStaleAfterSeconds attribute from it's default of 60 seconds.

4.10 Changing the Location of NeatUpload's Files

By default, NeatUpload looks for all the files it needs in the NeatUpload folder under your application root. If you want to move some of those files to other locations, this section will help tell NeatUpload where to find them.

If you move default.css, cancel.png, refresh.png, or stop_refresh.png, you need to customize Progress.aspx to refer to them in their new locations.

If you move Progress.aspx, you need to set the ProgressBar's Url property to refer to the new location.

If you move MultiRequestUploadHandler.ashx, you need to set the <neatUpload> element's multiRequestUploadHandlerUrl attribute to refer to the new location. You also need to ensure that sufficiently large requests are allowed and the upload module is enabled for the new location. The Web.config file in the NeatUpload folder does that.

If you move UploadStateStoreHandler.ashx, you need to set the handlerUrl attribute of the <add> element that adds the AdaptiveUploadStateStoreProvider so that it refers to the new location.

If you move Error413.aspx, you need to change any <customErrors> section of your Web.config that refers to that page to refer to the new location.

NeatUploadConfig.xsd is only used to provide Intellisense for the <neatUpload> section. It can be relocated to any location within your application without affecting Intellisense support. If you remove it entirely, your application will still function normally.

4.11 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".

4.12 Changing the Encryption and Validation Algorithms Used

By default, NeatUpload protects data that must pass through an untrusted 3rd-party (e.g. the StorageConfig) by using .NET's default SymmetricAlgorithm and HashAlgorithm. You can use the decryption and validation attributes to change the algorithms that NeatUpload uses,. For example, the following will cause values will allow NeatUpload to work in a FIPS compliant environment:

<neatUpload ... decryption=”3DES” validation=”SHA1” ... />

5 Customization

5.1 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.
ProcessingInProgress - an upload (if any) has completed and the code associated with the page is running. See Showing Processing Progress for details.
Completed - the entire upload was successfully received and processed
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 NeatUpload  version /dotnet/src/Brettle.Web.NeatUpload/NeatUpload.Strings.resx.  NeatUpload throws an NonfilePortionTooLargeException if the non-file portion of an upload request is more than the size specified with the maxNormalRequestLength attribute of the <neatUpload> element.  The value of the NonfilePortionTooLargeException's Message property is based on the NonfilePortionTooLargeMessageFormat resource in NeatUpload  version /dotnet/src/Brettle.Web.NeatUpload/NeatUpload.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 NeatUpload  version /dotnet/src/Brettle.Web.NeatUpload/NeatUpload.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.

5.2 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>

5.3 Advanced Progress Bar Customization

NeatUpload's ProgressBar (and the GreyBoxProgressBar extension) are both subclasses of NeatUpload's abstract ProgressBarBase class. ProgressBarBase provides all of the functionality of the progress bar including detecting postbacks initiated by registered triggers, displaying the progress bar in a pop-up window, and displaying fallback content when scripting is not available. The ProgressBar control adds an Inline property that displays the progress bar inline using an IFRAME if the browser supports it. GreyBoxProgressBar displays the popup using GreyBox instead of window.open(). You can create your own subclass of ProgressBarBase to change the way that the progress bar is displayed. Refer to the source code of GreyBoxProgressBar for an example of how to do that. The key is to override GetStartupScript() to return the base startup script followed by your own script. The base startup script will create the NeatUploadPB script object associated with your control. You can access the created object using NeatUploadPB.prototype.Bars[' progressBarID '] where progressBarID is the ClientID property of your control. Once you have that object, you can modify it's behavior by replacing any of the following:

Display() - NeatUpload calls Display() with no parameters when the progress bar should be displayed. The default implementation calls DisplayUrl(progressUrl) which is described below. You might want to override the Display() method to show a hidden progress bar before calling DisplayUrl().

DisplayUrl(progressUrl) – The default implementation of Display() calls DisplayUrl(progressUrl) to start the progress display. The progressUrl parameter is the URL of the progress page (e.g. Progress.aspx) with the required query string. The default DisplayUrl() passes progressUrl to window.open() to start the progress display in a popup window. You might want to override DisplayUrl() to display the progress information in some other way.

EvalOnCloseProgressPage subclasses (e.g. Progress.aspx) define a NeatUploadClose() function which is responsible for closing the progress display created by DisplayUrl(). ProgressPage ensure that NeatUploadClose() is called when the upload completes or is canceled. Your subclass can call NeatUploadClose() at other times (e.g. when an upload is rejected). The default NeatUploadClose() implementation attempts to access NeatUploadPB.prototype.Bars[' progressBarID '].EvalOnClose within the window object that started the display. If the upload did not complete (e.g. it was canceled), NeatUploadClose() will be able to access the EvalOnClose string and will pass it to JavaScript's eval function in the window or frame that displays the progress page (e.g. Progress.aspx). You might set EvalOnClose to some code that hides the progress bar that you display in DisplayUrl(). You can access the window object that started the display using the NeatUploadMainWindow variable. If the upload completed and the uploading page has already been replaced (or is being replaced) by the response from the server, then NeatUploadClose() won't be able to access EvalOnClose and will be unable to execute that code. In that case, NeatUploadClose() will call window.close() so that the progress display will be closed if it is displayed in a separate top-level window. Note that if the progress display was embedded in the upload page, it would have been effectively closed when the upload page was replaced, so NeatUploadClose() ignores that case. If you want different behavior from NeatUploadClose(), you can redefine it in your ProgressPage subclass (e.g. in a custom Progress.aspx).

5.4 Customizing the MultiFile Button

By default, the MultiFile control renders as a “Pick Files...” button that displays a file selection dialog when clicked. You can replace that button with a different button, a link, or any other HTML you want by putting the desired content inside the MultiFile element on your page. Note that whatever content you place in the MultiFile element is for display purposes only. The user won't actually interact with any of that content. If the user attempts to click anywhere on your content, the file selection dialog will be displayed. For example, the following:

<Upload:MultiFile runat=”server”>
<a href=”http://www.brettle.com/neatupload”>Select Files...</a>
</Upload:MultiFile>

will display a “Select Files...” link instead of the “Pick Files...” button, but clicking on the link will display the file selection dialog instead of loading the NeatUpload home page.

5.5 Customizing MultiFile's Handling of Queued Files

By default, NeatUpload's MultiFile displays queued files (i.e. files that have been selected but not yet uploaded) as a vertical list above the “Pick Files...” button, with each name preceded by an “X” which the user can click to delete the file. To display the list someplace else, simply set MultiFile's FileQueueControlID property to the ID of the element (e.g. a <div>) that should contain the list. To display the list in some other format, replace the OnFileQueued() method of the NeatUploadMultiFile script object that NeatUpload creates for your control. You can access that object using NeatUploadMultiFile.prototype.Controls['multiFileID'] where multiFileID is the ClientID property of your control. The OnFileQueued() method takes one argument: a file object that contains a name property which your OnFileQueued() method should display and a Delete() method which your OnFileQueued() method should arrange to call when the user deletes the file from the queue. For an example, see the default implementation of OnFileQueued() in NeatUpload- version /js/NeatUpload.js.

The file object passed to OnFileQueued() also has a size property. If the file was selected using Flash, that property will contain the size of the file in bytes. Otherwise, it will contain -1. You could use that property to warn the user and immediately call Delete() for any files which are larger than you want.

5.6 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 a MultiFile or 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 MultiFile or InputFile control associated with the upload, the browser-specified name and MIME type of the file, and the value of the 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. Various InputFile members delegate to the associated methods of your UploadedFile-derived object. The MultiFile control has a Files property that returns an array of your UploadedFile-derived objects directly.

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. NeatUpload responds to  UploadExceptions by throwing an HttpException with the same HTTP error code and the UploadException as the InnerException. This allows you to 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.

5.7 Creating a Custom UploadStateStoreProvider

By default, NeatUpload uses the built-in AdaptiveUploadStateStoreProvider which maintains the state of uploads in application state if the session state mode is Off or InProc, and otherwise maintains state in the user's session so that it can be shared across a web garden or web farm. You can force NeatUpload to maintain upload state in application state or the user's session by configuring NeatUpload to use the InProcUploadStateStoreProvider or SessionBasedUploadStateStoreProvider, respectively.

In addition, you can create your own UploadStateStoreProvider subclass which maintains upload state differently. You will need to implement the following methods:

public abstract void Initialize(string name, NameValueCollection attrs);
public abstract UploadState Load(string postBackID); public abstract void MergeAndSave(UploadState uploadState); protected virtual void Delete(string postBackID);
public virtual CleanUpIfStaleCallback GetCleanUpIfStaleCallback();

For an example of how to implement a custom UploadStateStoreProvider, see InProcUploadStateProvider.cs and SessionBasedUploadStateStoreProvider.cs.

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

6 Extensions

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

6.1.1 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  version /dotnet/app/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  version /dotnet/app/bin/ directory.

6.1.2 Configuration

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

<configuration>
<configSections>
<section name="neatUpload" type="Brettle.Web.NeatUpload.ConfigSectionHandler, Brettle.Web.NeatUpload" allowLocation="true" />
</configSections>
...
<neatUpload ...
defaultStorageProvider="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>
...
</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.

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

6.1.4 Example

There is a demo at NeatUpload  version /dotnet/app/Extensions/ Hitone.Web.SqlServerUploader /DBWrite.aspx. Before using it for the first time, you need to:

  1. Create a database named "FileStorageTest" on an SQL Server.

  2. Create the table and stored procedures by running the query below corresponding to your version of SQL Server.

  3. If necessary, change the connection strings in NeatUpload  version /dotnet/app/Extensions/Hitone.Web.SqlServerUploader / Web.config to point to your database. For example, if you are using SQL Server Express on your local machine, you'll need to change "Server=.;" to "Server=. \ SQLEXPRESS;" in several places.

6.1.4.1 Example Setup Query for SQL Server 2005

CREATE TABLE [FileTable] (
        [Id] [int] IDENTITY (1, 1) PRIMARY KEY NOT NULL ,
        [FileName] [nvarchar] (50) NULL,
        [DataField] [varbinary] (max) 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

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 (0x,@FileName,@MimeType,1)
        Select @Identity = SCOPE_IDENTITY()
        Select @Pointer = DataField From FileTable Where $IDENTITY = @Identity
End

Go

CREATE 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 = 0x, -- Because @pointer is not used
                        @Size = DATALENGTH(DataField),
                        @FileName = [FileName],
                        @MIMEType = MIMEType
        From FileTable Where $IDENTITY = @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
        SELECT SUBSTRING(DataField, @Offset+1, @Size) FROM FileTable WHERE ($IDENTITY = @Identity)
End

Go

CREATE 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
        UPDATE FileTable SET DataField.write(@Bytes, null, null) WHERE $IDENTITY = @Identity
End

Go

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

Go

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

Go

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

Go

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

6.1.4.2 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

6.2 The GreyBoxProgressBar Extension

GreyBoxProgressBar is a NeatUpload extension that uses Orangoo's open-source GreyBox to display progress in a “pop-up window that doesn't suck”. To use the GreyBoxProgressBar control:

  1. Install GreyBoxProgressBar:

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

  2. Download and install GreyBox into a “/greybox” folder of your application. If you wish to put GreyBox someplace else, use the GreyBoxRoot attribute of the GreyBoxProgressBar control to specify the URL of the GreyBox directory. You may use an absolute or relative URL that refers to a page in the same web application. If the URL starts with "~", the "~" will be replaced with the web application root. By default, "~/greybox" will be used.

  3. Add the GreyBoxProgressBar to your forms:
    If you are using Visual Studio Web Form Designer, simply drag the GreyBoxProgressBar 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="GreyBoxUpload" Namespace="Brettle.Web.NeatUpload" Assembly="Brettle.Web.NeatUpload.GreyBoxProgressBar "%>

    and add a GreyBoxProgressBar control to your aspx page wherever you want the user to choose a file, using something like this:

    <GreyBoxUpload:GreyBoxProgressBar id="progressBarId" runat="server" />
  4. Optionally, set any properties that you would normally set on an ProgressBar control, except the for the Inline property. If you set the Width or Height properties, they must be set to values in units of pixels.

For an example of using the GreyBoxProgressBar extension, see Demo.aspx and Demo.aspx.cs in the NeatUpload  version /dotnet/app/Extensions/Brettle.Web.NeatUpload.GreyBoxProgressBar/ directory.

6.3 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  version /dotnet/app/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  version /dotnet/app/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>
    <section name="neatUpload" type="Brettle.Web.NeatUpload.ConfigSectionHandler,Brettle.Web.NeatUpload"
    allowLocation="true" />
    </configSections>
    ...
    <neatUpload ...
    defaultStorageProvider="HashingFilesystemUploadStorageProvider">
    <providers>
    <add name="HashingFilesystemUploadStorageProvider"
    type="Brettle.Web.NeatUpload.HashingFilesystemUploadStorageProvider,Brettle.Web.NeatUpload.HashedInputFile"
    algorithm="MD5"/>
    </providers>
    </neatUpload>
    ...
    </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 HashedDemo.aspx and HashedDemo.aspx.cs in the NeatUpload  version /dotnet/app/Extensions/Brettle.Web.NeatUpload.HashedInputFile/ directory.  For a more complex configuration example, see Web.config in that directory.  It uses location filtering and is designed to work in conjuction with the Web.config in application root.

7 Known Issues

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

7.2 Permissions on uploaded files depend 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.

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

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

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

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

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

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

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

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

7.11 NeatUpload controls might not work properly within an UpdatePanel

To maintain compatibility with ASP.NET 1.1, NeatUpload's controls do not call some methods that might be needed to ensure proper operation within an UpdatePanel control. To avoid those issues:

7.12 NeatUpload might fail when using Windows Authentication

The SessionBasedUploadStateStoreProvider stores upload state in the user's session by making anonymous HTTP requests to NeatUpload/UploadStateStoreHandler.ashx. If the server requests authentication, NeatUpload will throw an exception.

When Flash is used to upload files, the Flash player makes anonymous HTTP requests to NeatUpload/MultiRequestUploadHandler.ashx. If the server requests authentication, Flash will display an authentication dialog, but due to bugs in Flash will not be able to use that information to authenticate with the server and the upload will not occur.

For all of the above problems, the easiest workaround is to enable Anonymous Authentication and disable Windows Authentication for the NeatUpload folder. If you aren't using Flash and you don't have a web garden or web farm you can also avoid this issue by configuring NeatUpload to use the InProcUploadStateStoreProvider, as described in Reducing Server Load.

7.13 NeatUpload might fail when using SSL/HTTPS

The SessionBasedUploadStateStoreProvider stores upload state in the user's session by making requests to NeatUpload/UploadStateStoreHandler.ashx using the same protocol (http or https) that the user used to do the upload. If https is used and the server's certificate is not trusted, NeatUpload will throw an exception complaining that "The remote certificate is invalid according to the validation process".

The easiest workaround is to configure ASP.NET to accept your cert, probably in your Global.Application_Start(). If you don't have a web garden or web farm you can also avoid this issue by configuring NeatUpload to use the InProcUploadStateStoreProvider, as described in Reducing Server Load.

7.14 Application restarts can interrupt uploads or blank ProgressBars

ASP.NET can restart your application for a variety of reasons, including changes to your Web.config(s), aspx files, the contents of your bin folder, or App_LocalResources folder(s). It will also restart your application if you delete directories from anywhere within your application's directory hierarchy, including App_Data. If you aren't using NeatUpload, you might not notice these restarts because requests that are already in process finish normally, and new requests are handled by a new instance of your application. To determine if your application is being restarted, configure your application to log application lifetime events to the event log.

Restarts are important because application state and, if your session state mode is InProc, all session state is lost during a restart. That loss of state can cause problems for NeatUpload because it stores the state of an in-progress upload in either application state or session state.

You can prevent ASP.NET from doing some application restarts by setting HKLM\Software\Microsoft\ASP.NET\FCNMode=1 but IIS7 will still restart your application if you change the root Web.config and you will undoubtedly need to restart your application manually on occasion. To completely workaround the issue use a session state mode other than InProc or Off and use the default AdaptiveUploadStateStoreProvider or the SessionBasedUploadStateStoreProvider. If you do not, you may experience the following problems:

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