by Dean
Brettle
Last updated
Table of Contents
1.1 Requirements
1.2 Branding, Licensing, and the Trademark
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.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.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.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.8 Configuring Individual InputFile and MultiFile Controls Programmatically
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.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.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.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
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.
To use NeatUpload you will need:
.NET Framework (1.1, 2.0, 3.0, or 3.5), or Mono (1.1 or 1.2). Mono users that are using the mod_mono Apache module should use mod_mono 1.1.14 or later.
A working web application.
Either "Full" trust or, if NeatUpload is installed in the GAC, at least "Medium" trust. "Full" trust level is the ASP.NET default, but some hosting providers force web applications to run with "Medium" trust.
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:
spread the word by leaving the branding in place, or
becoming familiar enough with NeatUpload (as evidenced by your ability to find and remove the branding) that you could contribute code in the future, or
make a substantial non-monetary contribution (e.g. fix a bug, help with testing, or manually translate documentation), in return for which I will typically provide an unbranded version, or
help fund NeatUpload development by purchasing an unbranded version for $400 US which you can pay here. Once I receive payment, I will email you a link to a version of NeatUpload with the branding removed. Note that unbranded version comes with no warranty (just like the branded version) and I can't provide refunds. So you should make sure that the branded version works properly in your application before purchasing the unbranded version. Of course, if you have problems with either version, please let me know.
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.
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.
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:
You should disable Windows Authentication and enable Anonymous Authentication for the NeatUpload folder.
The InputFile
control is now "branded" unless you are using a version
that someone has modified to remove the branding. The branding looks
says “(Powered by NeatUpload)”.
For more information on the branding and having it removed, see
Branding, Licensing, and the Trademark
If you don't
specify a temporary directory for use by the default
FileSystemUploadStorageProvider
, it
will first try to use the directory app_data/NeatUpload_Temp
within your application directory. If that directory exists (or can
be created) and is writable, NeatUpload will use it. Otherwise, it
will revert to the old behavior of using the system-wide temporary
directory. This change was made to make it easier to configure
NeatUpload in environments which use a Medium trust-level. In such
an environment, the server administrator typically doesn't want to
give applications access to the system-wide directory. Without this
change, you would need to explicitly specify a temporary directory
for NeatUpload to work in such an environment. With this change, you
at most need to create app_data/NeatUpload_Temp
and grant write permissions to the account that your app runs under.
If you aren't in a Medium trust environment, you don't even need to
do that. NeatUpload will probably be unable to create the directory
and will fallback to using the system-wide temporary directory.
UploadException
is no longer a subclass of HttpException
.
It is now just a subclass of Exception
.
This changes was necessary to allow UploadException
s
to be serialized so they can be passed to the progress bar in a web
garden/farm environment. Custom error pages should still work
because when an UploadException
occurs
during an upload, NeatUpload now wraps it in an HttpException
with the same HTTP status code and throws the HttpException
.
For upload
requests where the non-file portion exceeds the
maxNormalRequestLength
limit,
NeatUpload now throws a NonfilePortionTooLargeException
instead of an UploadTooLargeException
.
If you use
ASP.NET 2.0 themes, those themes will now be applied to the default
progress display page (Progress.aspx
)
and the sample custom error page (Error413.aspx
).
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 ProgressBar
s
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.
Added support for IIS7's default application pool (i.e. Integrated Pipeline Mode) using the same technique that Darren Johnstone's upload component uses. The technique does use reflection to access non-public members of ASP.NET classes, but uses the least reflection of any technique I've seen.
Added the
MultiFile
control for uploading
multiple files.
If the user has Flash 8 or above and a compatible browser, they can also select multiple files from a single file selection dialog. 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 browser that don't have the required support (e.g. Opera 9 and Linux Firefox 2) can select one file per-dialog.
Developers can customize the button that displays the file selection dialog and customize the handling of queued files.
Added support for using NeatUpload in a web garden or web farm if you have ASP.NET configured to share session state across the servers in your garden/farm.
Added support for Using NeatUpload in a Medium Trust Environment when installed in the GAC.
Added support for
using a ProgressBar
to show processing
progress. This can be used to display the progress of a
long-running task even when no files are uploaded.
Added an UnloadConfirmer
control which
displays a confirmation dialog to the user if they attempt to leave
the page or resubmit it while an upload is in progress.
Added support for
uploading without using
InputFile
.
Added support for Limiting the Upload Rate.
Added APIs and SPIs to make it possible to write new controls that
use the same IHttpModule
, new
IHttpModule
s that work with existing
controls, and custom UploadStateStoreProvider
s
that maintain upload state in different locations.
Added Intellisense support for the <neatUpload> element if you include NeatUploadConfig.xsd in your web project and use <neatUpload xmlns="http://www.brettle.com/neatupload/config/2008" ...>.
Rearranged the directory structure to separate the source code from the example application and moved most of the script code into a separate NeatUpload.js file that is not specific to ASP.NET.
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 .
Installing NeatUpload is pretty straightforward. Just copy the
necessary assemblies and subdirectories into your application and
make some modifications to your Web.config
.
Specifically:
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.
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:
Use Windows
Explorer to open %WINDIR%/assembly
.
The window will list the assemblies currently in the GAC along with
their version number, etc.
In another
Windows Explorer window, open the NeatUpload-
version /dotnet/app/bin
folder.
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.
Find the Brettle.Web.NeatUpload assembly that was just added and note the value in the "Version" column.
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.
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.
(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.
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.
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.
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.
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" />
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"/>
(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.
(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.
To use the Visual Studio web form designer to add NeatUpload to a web form, follow these steps:
Drag the
following controls from the toolbox onto your web form: MultiFile
,
InputFile
, ProgressBar
,
and Button
.
(Optional) If you
want an inline progress bar, set the ProgressBar
's
Inline
property to true
.
(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.
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.
To use NeatUpload on a web form without using the Visual Studio designer, follow these steps:
Add the following to the top of your page:
<%@ Register TagPrefix="Upload" Namespace="Brettle.Web.NeatUpload" Assembly="Brettle.Web.NeatUpload" %>
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.
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.
Add a submit button to your aspx page. For example:
<asp:Button id="submitButtonId" runat="server" Text="Submit" />
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;
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.
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.
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.
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.
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
.
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:
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.
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.
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.
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:
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.
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.
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.
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 ProgressBar
s 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
UploadedFile
. UploadModule.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.
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" />
...
<neatUpload>
Configuration SectionTo 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.
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
" ... />
</neatUpload>
...
</providers>
If you specify a relative path, it will be relative to your application root directory.
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.
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.
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.
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:
For upload
requests where the non-file portion exceeds the
maxNormalRequestLength
limit,
NeatUpload throws a NonfilePortionTooLargeException
instead of an UploadTooLargeException
.
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()
.
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.
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:
Only use
StorageConfig
to configure a MultiFile
or InputFile
control if you don't
mind an attacker associating that StorageConfig
with other MultiFile
or InputFile
controls while the same key is being used. This is not
typically a problem for StorageConfig["tempDirectory"]
because it is mostly used to specify a temporary directory on the
same drive as the final location of the file to improve performance.
If an attacker used a StorageConfig
from a different control, they would typically just get poor
performance. Note, however, that if you decide to stop using a
temporary directory, you should either change the key or use some
other mechanism (e.g. change the permissions on the directory) to
ensure that an upload that uses an old StorageConfig
can not write to the old directory.
Only use
StorageConfig
to configure a MultiFile
or InputFile
control if you don't mind
having uploads rejected while transitioning from old to new keys.
NeatUpload will reject any uploads from forms rendered with
the old key. The default rejection message is the value of the
InvalidStorageConfigMessageFormat
resource in NeatUpload
version
/dotnet/src/Brettle.Web.NeatUpload/NeatUpload.Strings.resx
.
It defaults to "Please refresh the page and try again."
To use StorageConfig
in an
environment (e.g. a web farm) where the server rendering the form
could be different from the server that receives the upload request,
you must manually configure all servers to use the same keys.
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"/>
</neatUpload>
...
</providers>
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.
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.
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".
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” ... />
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 |
---|---|
|
A value from the |
|
Number of bytes received by the server so far. Pass this
to |
|
Total number of bytes in the upload. Pass this to
|
|
The units associated with the result of a call to
|
|
Converts a number of bytes to units that would make |
|
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. |
|
Converts a rate to more readable units. If |
|
A double in the range 0.0-1.0. Computed as
|
|
|
|
|
|
Formats a string format; |
|
An |
|
An |
|
The client-specified name of the file currently being received by the server. |
|
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. |
|
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. |
|
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. |
|
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:
Use all lowercase
for HTML element names (ie '<body>
'
instead of '<BODY>
' or '<Body>
').
If your page
contains HTML-specific entities replace them with their numeric
equivalents. For example, replace '
'
with ' 
'.
Use <asp:HyperLink>
instead of <a>
for links,
especially for links that use CancelUrl
,
StartRefreshUrl
, and StopRefreshUrl
.
Some HtmlAnchor
(i.e.
<a>
) implementations, notably
Microsoft's ASP.NET 2.0, do not HTML encode the value of the href
attribute. If the URLs contain ampersands, the XML will not be
considered well-formed unless they are encoded.
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.
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>
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.
EvalOnClose
– ProgressPage
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
).
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.
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.
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:
updates the
progress bar to indicate that the upload was rejected (i.e. sets
Status
to Rejected
and Rejection
to the UploadException
that occured)
if the progress bar is in a pop-up window, leaves the pop-up open so that the user can see the message
asks the browser to stop the upload using javascript to simulate clicking the browser's Stop button
waits 5 seconds to give the browser and opportunity to stop the upload
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 UploadException
s
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:
updates the
progress bar to indicate that an error occurred (i.e. sets
Status
to Failed
and Failure
to the Exception
that occured)
if the progress bar is in a pop-up window, leaves the pop-up open so that the user can see the message
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.
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);
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.
When NeatUpload needs the UploadState
object associated with a particular post-back ID, it will call the
Load()
method which must return either
the UploadState
object, or null if the
post-back ID is not known.
When NeatUpload needs to update the UploadState
,
it will call the MergeAndSave()
method.
That method must atomically merge the passed UploadState
object with the stored UploadState
object and replace the stored UploadState
object with the merged result. The static
UploadStateStoreProvider.Merge()
helper
method can do the actual merging once your provider has both
UploadState
objects.
When NeatUpload determines that an UploadState
object is stale, it will call the Delete()
method which must delete the UploadState
object from the state store. Delete()
is called from the protected CleanUpIfStale()
method. CleanUpIfStale()
calls Load()
to retrieve the UploadState
object from
the store, and then calls Delete()
if
it is stale. By default, the CleanUpIfStale()
method runs on a thread which does not have any HttpContext
.
So, if your Load()
or Delete()
implementations require an HttpContext
,
you will need to implement:
public virtual CleanUpIfStaleCallback GetCleanUpIfStaleCallback();
to return a method that calls CleanUpIfStale()
in an a suitable environment. For example,
InProcUploadStateStoreProvider.GetCleanUpIfStaleCallback()
saves a reference to the HttpApplicationState
object and returns a method that makes it available to the provider
(via a ThreadStatic
field) when the
returned method calls CleanUpIfStale()
.
For an example of how to implement a custom
UploadStateStoreProvider
, see
InProcUploadStateProvider.cs
and
SessionBasedUploadStateStoreProvider.cs
.
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.
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
.
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.
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:
createProcedure
- Procedure that takes @FileName and @MIMEType (both varchars) input
parameters, and sets @Identity (Numeric) and @Pointer (Binary(16))
output parameters. The @Identity and @Pointer values that are
returned will be passed to the writeProcedure
as the file is received. The @Identity value will also be passed to
the deleteProcedure
, cleanupProcedure
,
renameProcedure
, and
storeHashProcedure
.
writeProcedure
- Procedure that takes @Identity (Numeric), @Pointer (Binary(16)),
@Offset (Int), @Delete (Int), and @Bytes (varbinary) input
parameters. It should replace the @Delete bytes at position @Offset
in the file identified by @Identity and @Pointer with the bytes in
@Bytes.
cleanupProcedure
- The provider calls this procedure with @Identity (Numeric) when an
upload is no longer in progress and it will not call writeProcedure
anymore. Note that this procedure is called even if the upload did
not complete successfully for some reason.
storeHashProcedure
- If the hashAlgorithm
attribute is
set, the provider calls this procedure with @Identity (Numeric) and
@Hash (varchar). @Hash is the hash value computed for the file
identified by @Identity.
renameProcedure
- The provider calls this procedure with @Identity (Numeric) and
@FileName (varchar) to indicate that the application has requested
that the name of the file identified by @Identity be changed to
@FileName. This happens when you call inputFileId
.MoveTo()
.
deleteProcedure
- Procedure that takes @Identity (Numeric) input parameter and
deletes the specified file. This is called at the end of the request
if your application called neither inputFileId .MoveTo()
nor
inputFileId .Verify()
openProcedure
- This procedure is called if you use inputFileId
.FileContent
to access the uploaded
file as a stream. It takes an @Identity (Numeric) input parameter,
and sets @Pointer (Binary(16)), @FileName (varchar), and @MIMEType
(varchar) output parameters. The @Pointer value that is returned
will be passed to the readProcedure
as
content is read from the stream.
readProcedure
- This procedure
is called if you use inputFileId .FileContent
to access the uploaded file as a stream. It takes @Identity
(Numeric), @Pointer (Binary(16)), @Offset (Int), and @Size (Int)
input parameters and returns the @Size bytes starting at position
@Offset in the file identified by @Identity and @Pointer.
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:
tableName
- Name of the table to contain the files. "FileTable" in
the example below.
dataColumnName
- Name of an image/varbinary column for the contents of the uploaded
file. "DataField" in the example below.
partialFlagColumnName
- Name of a tinyint column that the provider will set to 1 while the
upload is being received and set to 0 when the upload is complete.
"Partial" in the example below.
fileNameColumnName
- Name of a nvarchar column for the name of the uploaded file. While
the upload is being received, the provider will set the column to
the name (excluding path provided by the browser). If you call
inputFileId.MoveTo(), the new name will be stored in this
column. "FileName" in the example below.
mimeTypeColumnName
- Name of a nvarchar column for the browser-provided MIME content
type of the file "MimeType" in the example below.
hashColumnName
- Name of a
nvarchar column for the hash of the file contents (as a hex string).
"FileHash" in the example below.
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.
To add the SqlServerInputFile
to your
forms:
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" />
Set any
properties that you would normally set on an InputFile
or HtmlInputFile
control.
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.
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:
Create a database named "FileStorageTest" on an SQL Server.
Create the table and stored procedures by running the query below corresponding to your version of SQL Server.
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.
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
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
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:
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.
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.
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" />
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.
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:
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.
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.
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" />
Set any
properties that you would normally set on an InputFile
or HtmlInputFile
control.
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.
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.
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.
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.
HttpResponse.AppendToLog()
does
nothing when UploadHttpModule
is usedDue 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.
HttpResponse.HeaderEncoding
does nothing when UploadHttpModule
is
usedDue 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.
Option 1: Set the Content-Disposition header to simply "attachment", and place the encoded filename in the "path info" part of the download URL. So, instead of using a download URL of "http://tempuri.org/DownloadFile.ashx", you would use a download URL of "http://tempuri.org/DownloadFile.ashx/YourUrlPathEncodedFilename". If you are using IIS or some other webserver that supports path info, this approach will work under at least the latest versions of IE, Firefox, Opera, and Safari. However, this approach does not work if you are using Visual Studio's Development Web Server because it doesn't support path info. If your server does support path info, you can implement this approach easily by changing your download page to redirect the browser to a page which has the necessary path info, like this:
<%@ WebHandler Language="C#" Class="Handler" %> using System; using System.Web; public class Handler : IHttpHandler { public void ProcessRequest (HttpContext context) { string filename = "中文檔名.doc" // or some other file name string encodedFilename = HttpUtility.UrlPathEncode(filename); if (context.Request.PathInfo == null || context.Request.PathInfo.Length == 0) { string query = context.Request.Url.Query; context.Response.Redirect(context.Request.Path + "/" + encodedFilename + query); } else { context.Response.Clear(); context.Response.ContentType = "application/octet-stream"; context.Response.AddHeader("Content-Disposition", "attachment"); FileInfo fi = new FileInfo(context.Server.MapPath(filename)); context.Response.WriteFile(fi.FullName); context.Response.Flush(); context.Response.End(); } } public bool IsReusable { get { return false; } } }
Option 2: Set the Content-Disposition header's filename
parameter to the UrlPathEncoded filename, but do so in a way that is
supported by the browser. According to RFC
2231, the correct way to do this is to set the
Content-Disposition header to "attachment; filename*=utf-8''
YourUrlPathEncodedFilename
". That works for the latest versions of Firefox and
Opera, but not IE and Safari. Both IE and Safari will ignore the
filename parameter and use the name of your download page (e.g.
DownloadFile.ashx). There is no way to set the filename parameter to
work with Safari, but for IE you can set Content-Disposition header
to "attachment; filename=
YourUrlPathEncodedFilename
". That doesn't work for Firefox or Safari though. So,
you need to treat it as a special IE-only case. Here is an example:
<%@ WebHandler Language="C#" Class="Handler" %> using System; using System.Web; public class Handler : IHttpHandler { public void ProcessRequest (HttpContext context) { string filename = "中文檔名.doc" // or some other file name string encodedFilename = HttpUtility.UrlPathEncode(filename); context.Response.Clear(); context.Response.ContentType = "application/octet-stream"; string contentDisposition = "attachment"; if (context.Request.Browser.IsBrowser("IE")) contentDisposition += "; filename=" + encodedFilename; else contentDisposition += "; filename*=utf-8''" + encodedFilename; context.Response.AddHeader("Content-Disposition", contentDisposition); FileInfo fi = new FileInfo(context.Server.MapPath(filename)); context.Response.WriteFile(fi.FullName); context.Response.Flush(); context.Response.End(); } public bool IsReusable { get { return false; } } }
ProgressBars
don't work with
OperaOpera 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.
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.
When not using NeatUpload, an event handler returning false will
prevent the browser from performing the default action for the event.
For example, consider a submit button with
onclick="return confirm('Submit?');"
.
If the user clicks the button a dialog will appear asking "Submit?".
When not using NeatUpload, declining will prevent submission of the
form. However, when using NeatUpload, the form will be submitted if
the user is using IE6. To workaround this, change such handlers to
set window.event.returnValue=false
when
they return false. For example, here is the implementation of a
function that can be used instead of confirm()
in the above example:
function confirmSubmit(msg)
{
var confirmed = confirm(msg);
if (!confirmed && window.event)
window.event.returnValue = false;
return confirmed;
}
This problem is caused by a difference in the order IE6 calls event
handlers. In order to prevent non-triggers from starting an upload,
NeatUpload registers event handlers on the <form>
element for a variety of events (including onclick). Those handlers
clear the filename if the event is not coming from a trigger. In
standard-conformant browsers (i.e. modern browsers other than IE), it
is possible to register those handlers so that they run before other
handlers registered for controls within the form (e.g. submit
buttons). As a result, the value returned by the submit button
handler is what determines whether the form actually gets submitted.
In IE, the NeatUpload form event handler runs last and the value it
returns (always true) determines whether the form gets submitted.
NeatUpload can't determine the value returned by the submit button
handler and return it instead, so the above workaround is needed.
LinkButtons
do not start the
progress display in Internet Explorer for the MacNeatUpload 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.
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.
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:
Don't add a NeatUpload control to the DOM in response to an AJAX callback. For example, adding a NeatUpload control as a child control of the UpdatePanel, or changing the control's Visible property from False to True, would cause the control to be added to the DOM. To workaround this, consider keeping the control in the DOM all the time but using CSS to hide it or show it as necessary.
Don't try to upload files using an AJAX callback. This does
not work for ASP.NET's FileUpload
or
HtmlInputFile
controls either. To
workaround this, you must ensure that a full postback occurs. One
way to do that is to register the upload button as a PostBackTrigger
for the UpdatePanel
.
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.
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.
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:
Non-flash uploads will throw an exception if the
SessionBasedUploadStateStoreProvider
is
being used with a session state mode of InProc. The
SessionBasedUploadStateStoreProvider
maintains upload state in the user's session by making HTTP requests
to NeatUpload/UploadStateStoreHandler.ashx
.
If the session state mode is InProc, then an application restart
will cause the user's session to not exist in the new application
instance handling the request from the
SessionBasedUploadStateStoreProvider
.
To workaround this issue, avoid using the
SessionBasedUploadStateStoreProvider
with a session state mode of InProc. Instead, use a session state
mode other than InProc or Off, or use the default
AdaptiveUploadStateStoreProvider
or the
InProcUploadStateStoreProvider
.
ProgressBar
s will go blank and
Flash uploads will hang if the session state mode is InProc or Off
or the InProcUploadStateStoreProvider
is being used. ProgressBar
s will go
blank because they monitor progress by making separate requests to
the server and after the restart, those requests will be handled by
the new application instance. Flash uploads will hang because Flash
uses a separate request to upload each file and after the restart,
the requests will be handled by the new application instance which
can't associate them with an existing upload state. To workaround
this issue use a session state mode other than InProc or Off and use
the default AdaptiveUploadStateStoreProvider
or the SessionBasedUploadStateStoreProvider
.
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:
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.
Reproduce the problem.
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.