InvalidOperationException
when using ProgressBar
with EnableViewState="false"
LinkButtons
do not start the progress display in Internet Explorer
for the MacProgressBar
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. Brettle.Web.NeatUpload.dll
with the copy from NeatUpload-1.1.x and add
Progress.js
and Error413.aspx
from NeatUpload-1.1.x/NeatUpload/
to the NeatUpload/
subfolder in your web application.
If you have not customized your Progress.aspx
page, you
should replace it with NeatUpload-1.1.x/NeatUpload/Progress.aspx
.
If you have customized it, consider changing it to use
the new <Upload:DetailsSpan>
and <Upload:DetailsDiv>
elements to take advantage of AJAX refreshless updates
. See Customizing
Progress.aspx for details.<neatUpload>
configuration section which can be
placed either in the <brettle.web>
or the <system.web>
section
group,
either of which can be put inside of the
<location>
element to apply
configuration settings to specific
pages.UploadStorageProvider
s.<Upload:DetailsSpan>
and <Upload:DetailsDiv>
elements.Url
property to ProgressBar
control to specify location of progress display page (defaults to NeatUpload/Progress.aspx
).Button.Click
handlers).Triggers
property
to ProgressBar
which can be used instead of
the ProgressBar.AddTrigger()
method.InputFile.FileName
property to always return just the
client-side file name (e.g. foo.txt
), even if
the browser sends the
full path (e.g. C:\dir\foo.txt
).
Since, Internet Explorer is the
only popular browser to send the full path, and the full path is
difficult to handle reliably on non-Windows servers which don't use
backslash ("\") as a path separator, returning the full path was likely
to result in code which is browser-specific and not portable.Progress.js
to reduce bandwidth required to download Progress.aspx
repeatedly.Demo.aspx.cs
from Brettle.Web.NeatUpload.dll
. It is now compiled on the fly when Demo.aspx
is requested.NeatUpload allows ASP.NET developers to stream uploaded files to disk and allows users to monitor upload progress. It is open source and works under Mono's XSP / mod_mono as well as Microsoft's ASP.NET implementation.
NeatUpload contains four custom controls (InputFile
, ProgressBar
, DetailsSpan
, and DetailsDiv
), an HttpModule
(UploadHttpModule
), and a Page subclass (ProgressPage
).
This section briefly describes what they each do and how they are
related. The remainder of this manual describes how to install and use
NeatUpload.
InputFile
is a custom control that renders like HtmlInputFile
,
but provides properties to access the
uploaded file's client-specified name, content, and MIME type, and a method to
move the file to a permanent location.
ProgressBar
is a custom control that is responsible for providing a site for the
progress to be displayed. It provides an attribute to control whether
to display the progress inline or in a popup. It also provides ways to
control which buttons cause the progress
display to start refreshing. If the progress is displayed inline, the ProgressBar
control renders as an IFRAME
. Otherwise, it renders as a DIV
containing
a "Check Upload Progress" link (to display the progress in a new
window) along with some JavaScript which removes the DIV
when the page
loads. This provides a fallback when JavaScript is not available. Note
that ProgressBar
doesn't actually display the
progress bar itself. It merely provides a site (an IFRAME
or popup)
which loads a page derived from ProgressPage
(by default Progress.aspx
). The ProgressPage
subclass displays the progress bar.
UploadHttpModule
is an HttpModule
that intercepts HTTP
requests, streams InputFile
uploads to
temporary files, and restricts the size of the remainder of the
request. By default, UploadHttpModule
intercepts every
request. As a result, a bug in NeatUpload could affect pages that don't even contain InputFile
controls. For this reason, you can configure NeatUpload to only use UploadHttpModule
for certain pages (or even none) while continuing to use InputFile
and ProgressBar
. When UploadHttpModule
is not used for a request, NeatUpload gets the uploaded file from the ASP.NET standard HttpRequest.Files
property instead of intercepting the request. That means that InputFile
will function like an HtmlInputFile
control,
but no progress bar will be displayed. This greatly reduces the risk of
adopting NeatUpload.
ProgressPage
is a subclass of System.Web.UI.Page
that is used as the base class for pages displayed in the site provided by the ProgressBar
control (i.e. in the IFRAME
or popup). Progress.aspx
is the default ProgressPage
. ProgressPage
retrieves
the details of the upload process from the UploadHttpModule
. and makes them available for subclasses to use in data-binding expressions. ProgressPage
subclasses (e.g. Progress.aspx
) place such data-binding expressions within DetailsSpan
and DetailsDiv
controls so that NeatUpload can use AJAX techniques to update the values without requiring a browser a refresh.
To use NeatUpload you will need:
Installing NeatUpload is pretty straightforward. Just copy
the necessary assemblies and subdirectories into your application and
make some
modifications to your Web.config
. Specifically:
NeatUpload-1.1.x/
. NeatUpload-1.1.x/
is a web application. Then point your browser at the Demo.aspx
on that
website. To use NeatUpload in your own web application, continue with
the remaining installation steps. NeatUpload-1.1.x/bin/Brettle.Web.NeatUpload.dll
, click Open, and then click
OK. A reference to Brettle.Web.NeatUpload.dll
will
automatically be added to your project the first time you use the
designer to add one of the NeatUpload controls to a form.Brettle.Web.NeatUpload.dll
in the NeatUpload-1.
1.x
/bin
directory. NeatUpload/
subdirectory in
your web application by copying
NeatUpload-1.
1.x
/NeatUpload/
and its contents. Don't copy the whole NeatUpload-1.
1.x
/
directory, just the NeatUpload-1.
1.x
/NeatUpload/
subdirectory. That subdirectory contains Progress.aspx
and associated files. Web.config
under configuration/system.web/httpModules
:
<add name="UploadHttpModule" type="Brettle.Web.NeatUpload.UploadHttpModule, Brettle.Web.NeatUpload" />
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.Demo.aspx
and Demo.aspx.cs
from NeatUpload-1.
1.x
/
into your application. Point your browser at Demo.aspx
and verify that the demo functions properly. To use the Visual Studio web form designer to add NeatUpload to a web form, follow these steps:
InputFile
, ProgressBar
,
and Button
.ProgressBar
's
Inline
property to true
.
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.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);
To use NeatUpload on a web form without using the Visual Studio designer, follow these steps:
<%@ Register TagPrefix="Upload" Namespace="Brettle.Web.NeatUpload" Assembly="Brettle.Web.NeatUpload" %>
InputFile
control to your
aspx page wherever you
want the user to choose a file, using something like this:
<Upload:InputFile id="inputFileId" runat="server" />Feel free to add any attributes that you would normally add to an
HtmlInputFile
tag. 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.
<asp:Button id="submitButtonId" runat="server" Text="Submit" />
InputFile
,
ProgressBar
, and Button
control using code like this:
using Brettle.Web.NeatUpload;
...
using System.Web.UI.WebControls;
...
public class YourPage : System.Web.UI.Page
{
...
protected InputFile inputFileId;
protected ProgressBar progressBarId;
protected Button submitButtonId;
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);
InputFile
and a ProgressBar
, the progress display is
started (either inline or in a popup). If all InputFiles
are empty, the progress display is not started. That ensures
that
the user is not distracted by a transient and meaningless progress
display.InputFile
controls
so that no files will be uploaded and the progress display will not
start. (On some downlevel browsers, notably Internet Explorer for
the Mac, NeatUpload can't clear the controls so it displays a dialog
asking the user to clear them manually and try again. The text
for that dialog can be customized via the ClearFileNamesAlert
resource in Strings.resx
.)ProgressBar
, each
ProgressBar
which specifies trigger controls will only start when
the form submission is initiated by one of those controls.ProgressBar
's
Triggers
property (multiple IDs
should be space-separated), or you can call the ProgressBar's AddTrigger(Control)
method. Note: if you call the AddTrigger(Control)
method, you need to call it each time the page is loaded (even when Page.IsPostBack
is true
) because the list of trigger controls
is not maintained in the page's ViewState
.<neatUpload>
Configuration SectionWeb.config
:<configuration>
<configSections>
<sectionGroup name="system.web">
<section name="neatUpload"
type="Brettle.Web.NeatUpload.ConfigSectionHandler,
Brettle.Web.NeatUpload" allowLocation="true" />
</sectionGroup>
</configSections>
...
sectionGroup
"name" attribute can
be either "system.web" or "brettle.web". Although using
"brettle.web" is better for preventing name collisions, it makes it
impossible to do location filtering under Mono (at least as of
1.1.9.2). Even under .NET, you will have to do more typing to
configure location filtering if you use "brettle.web" than if you use
"system.web". The remainder of this document assumes you are
using "system.web".Web.config
's
<system.web> element(s), like this:
<configuration>
...
<system.web>
...
<neatUpload
useHttpModule="true or false, defaults to true"
maxNormalRequestLength="
up to 2097151
in KBytes, defaults to 4096"
maxRequestLength="
up to 2097151
in KBytes
, defaults to
2097151
"
defaultProvider="friendly name, defaults to a
FilesystemUploadStorageProvider using the system temp dir">
<providers>
<add name="friendly
name"
type="type derived from
Brettle.Web.NeatUpload.UploadStorageProvider"
provider-specific-attributes
... />
<remove name="friendly name of provider"
/>
<clear />
</providers>
</neatUpload>
...
</system.web>
...
</configuration>
<providers>
element is
optional. You only need to use it if you are using an UploadStorageProvider
other than the default FilesystemUploadStorageProvider
,
or you need to change the temporary directory used by the FilesystemUploadStorageProvider
.
In those cases, you should include an <add>
element for
each provider or provider configuration you will be using and set the
<neatUpload> element's defaultProvider
attribute to the name you chose for the provider you want to use by
default. For specific pages or directories, you can override
that
default or add/remove/clear available providers using child
configuration sections. Child configuration sections are
<neatUpload> elements within <location>
elements or in
Web.config files in subdirectories. By default, NeatUpload puts uploaded files in temporary files
in the system's
temporary directory. You can specify a different directory
using the tempDirectory
attribute of the FilesystemUploadStorageProvider
,
like this:
<neatUpload ...If you specify a relative path, it will be relative to your application root directory.defaultProvider="FilesystemUploadStorageProvider" ...
><providers>
...
<add name="FilesystemUploadStorageProvider
"
type="Brettle.Web.NeatUpload.FilesystemUploadStorageProvider
"
tempDirectory="path, defaults to path returned by System.IO.Path.GetTempPath()"
... />
...</providers>
</neatUpload>
UploadHttpModule
is added via the <httpModules>
section of your Web.config
, it intercepts and
filters all requests
sent to your application. For upload requests, it streams the
uploaded files to disk and makes them available to your code via InputFile
.
It also limits the size of the non-upload part of the request
and the total size of
non-upload requests
because that request data is stored in server memory. If it
didn't do that an attacker could mount a Denial of Service attack by
sending a request that contains up to maxRequestLength
kilobytes of non-file data.UploadHttpModule
by
settting the <neatUpload>
element's useHttpModule
attribute to
false. If you want to use the UploadHttpModule
for only some
requests, you can use ASP.NET's <location>
element to
specify which pages it should be used for. You can also use
the <location>
element to
control which UploadStorageProvider
is used
for specific pages. For example,
consider the following configuration:<configuration>
...
<system.web>
...
<httpRuntime maxRequestLength="100" />
...
<neatUpload useHttpModule="false" maxNormalRequestLength="100"
maxRequestLength="2097151">
<providers>
<add name="special"
type="Brettle.Web.NeatUpload.FilesystemUploadStorageProvider"
tempDirectory="SpecialTempDirectory"
/>
</providers>
</neatUpload>
...
</system.web>
...
<location path="Demo.aspx">
<system.web>
<neatUpload useHttpModule="true"
/>
<httpRuntime
maxRequestLength="2097151" executionTimeout="3600" />
</system.web>
</location>
<location
path="Special.aspx">
<system.web>
<neatUpload useHttpModule="true"
defaultProvider="special" />
<httpRuntime
maxRequestLength="2097151" executionTimeout="3600" />
</system.web>
</location>
...
</configuration>
That configuration will cause the UploadHttpModule
to only be used for requests to Demo.aspx
and Special.aspx
.
In addition, ASP.NET's maxRequestLength
is set to only 100 KBytes for all requests other than those two pages.
For those two pages
, the maxRequestLength
is increased to 2 GBytes and the executionTimeout
is increased to 1 hour. Also, requests for Special.aspx
use the "special" provider which is configured to use the
"SpecialTempDirectory
", while requests for Demo.aspx
use the default provider which uses the system temporary directory.By default, NeatUpload does not directly limit the size of
uploads.
To limit upload size, use the <neatUpload>
element's maxRequestLength
attribute:
<neatUpload ... maxRequestLength="sizeInKBytes" ... />
If a user attempts to upload a file larger than the specified size, NeatUpload will update the progress bar to indicate that the upload was rejected because it was too large. If the progress bar is in a pop-up window, it will leave the window open to ensure that the user sees why the upload was rejected. Next it will attempt to stop the upload by asking the browser to run JavaScript that simulates clicking the browser's Stop button. If JavaScript is enabled, most modern browsers (including recent versions of IE, Firefox, and Opera) will be able to stop the upload and the user will simply see the original form along with the progress bar displaying the reason the upload was rejected. The remainder of this section only affects the user when the browser is not able to stop the upload.
After asking the browser to stop the upload using JavaScript, NeatUpload reads the remainder of the request and then throws
an UploadTooLargeException
. The UploadTooLargeException
class is a subclass of UploadException
(which is a
subclass of HttpException
) with an HTTP
status code of 413. If the browser hasn't stopped the upload
for some reason, the exception will cause the user to see a generic error
page by default. If you want something
more
user-friendly, you can either handle the error by adding a handler for
the HttpApplication.Error
event, or you can
use a custom
error page. NeatUpload comes with an example custom error
page. To
use it, add the following to your Web.config
under
configuration/system.web
:
<customErrors mode="On">
<error statusCode="413" redirect="~/NeatUpload/Error413.aspx" />
</customErrors>
Note:
most browsers, including IE and Firefox, won't display any new content until
the entire upload has been sent to the server. If that takes longer
than the amount of time specified via the httpRuntime
element's executionTimeout
attribute, the server may choose to close the connection and the user
will see a different error. IE will display a generic error page
saying "Page cannot be displayed". Firefox will display a dialog
saying "Document contains no data". The default executionTimeout
is 360 seconds.
By default, NeatUpload
restricts the size of
non-upload requests and the non-upload portion of upload requests to 4
MBytes. To specify a different maximum
size, use the <neatUpload>
element's maxNormalRequestLength
attribute:
<neatUpload ... maxNormalRequestLength="sizeInKBytes" ... />The behavior when this value is exceeded is similar to the behavior when the
maxRequestLength
is exceeded. See the preceding section for details. The
only difference is that for non-upload requests that exceed the maxNormalRequestLength
limit, NeatUpload does not attempt to use JavaScript to interrupt the browser and the exception that is thrown is an HttpException(413, "Request Entity Too Large")
instead of an UploadTooLargeException()
.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:HashedInputFile
control to your toolbox. To do that,
right-click
on the Toolbox, click Add/Remove Items, Browse to NeatUpload-1.1.x/HashedInputFile/bin/Brettle.Web.NeatUpload.HashedInputFile.dll
, click Open, and then click
OK. A reference to Brettle.Web.NeatUpload.HashedInputFile.dll
will
automatically be added to your project the first time you use the
designer to a HashedInputFile to a form.Brettle.Web.NeatUpload.HashedInputFile.dll
in the NeatUpload-1.
1.x
/HashedInputFile/bin
directory.HashingFilesystemUploadStorageProvider
. The simplest way to do that is to add the following to your Web.config
(see Configuration for more options):
<configuration>
<configSections>
<sectionGroup name="system.web">
<section name="neatUpload"
type="Brettle.Web.NeatUpload.ConfigSectionHandler,
Brettle.Web.NeatUpload"
allowLocation="true" />
</sectionGroup>
</configSections>
...
<system.web>
...
<neatUpload
defaultProvider="HashingFilesystemUploadStorageProvider">
<providers>
<add name="
HashingFilesystemUploadStorageProvider
"
type="Brettle.Web.NeatUpload.HashingFilesystemUploadStorageProvider, Brettle.Web.NeatUpload.HashedInputFile"
algorithm="MD5"/>
</providers>
</neatUpload>
...
</system.web>
...
</configuration>
algorithm
attribute above is passed to HashAlgorithm.Create(). The default value is "MD5", so the attribute could have been omitted above.HashedInputFile
control from your Toolbox and drop it on the form.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" />
InputFile or HtmlInputFile
control.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).HashedInputFile/HashedDemo.aspx
and HashedInputFile/HashedDemo.aspx.cs
. For a more complex configuration example, see HashedInputFile/Web.config
. It uses location filtering and is designed to work in conjuction with the Web.config
in parent directory.Progress.aspx
The Progress.aspx
page that comes
with NeatUpload is just an
example. You can modify it to suit your needs. For example, you
could change the color scheme with minor modification to the
default.css
stylesheet that is included, or
you could change the text
or images that are used by modifying Progress.aspx
itself. You could
even rearrange the layout entirely or create a new page derived from ProgressPage
and use the Url
property of the ProgressBar
to refer to it. To allow AJAX refreshless updates, you must include at least one DetailsSpan
or DetailsDiv
control. Those controls render as like div
and span
controls, respectively. Use data-binding expressions within those
controls (either the in the content or properties) to access the
details of the upload at runtime. Below is a list of the
properties and methods that NeatUpload provides for use in data-binding
expressions. Refer to the default Progress.aspx
for an example of how each of these is used.
Name | Description |
Status |
A value from the UploadStatus enumeration, as follows:Unknown - NeatUpload has not yet started receiving the uploadNormalInProgress - a normal (non-chunked) upload had started but not yet finishedChunkedInProgress - an upload which uses chunked transfer coding has started but not yet finished.Completed - the entire upload was successfully receivedCancelled - the user has cancelled the upload using the cancel linkRejected - the upload has been rejected by the server because it is unacceptable for some reasonFailed - an unexpected error occurred while receiving the uploadIf the WhenStatus property of a DetailsSpan and DetailsDiv control is set, the control will only render when the status is one of the space-separated list of statuses in the WhenStatus value. If the WhenStatus property is not set, the control will always display. |
BytesRead |
Number of bytes received by the server so far. Pass this to FormatCount() to get a more readable value. |
BytesTotal |
Total number of bytes in the upload. Pass this to FormatCount() to get a more readable value. For chunked uploads, BytesTotal is -1. |
CountUnits |
The units associated with the result of a call to FormatCount() . If BytesTotal is greater that 1 million, returns the value of the MBUnits resource. Otherwise, if BytesTotal is greater than 1
thousand, returns the value of the KBUnits resource.
Otherwise, returns the value of the ByteUnits
resource. Note: for chunked uploads, BytesRead is used instead of BytesTotal . |
FormatCount(long count) |
Converts a number of bytes to units that would make BytesTotal readable. If BytesTotal is greater that 1 million, count is formatted according to the MBCountFormat resource. Otherwise, if BytesTotal greater than 1 thousand, count is formatted according to the KBCountFormat resource. Otherwise, count is formatted according of the ByteCountFormat resource. Note: for chunked uploads, BytesRead is used instead of BytesTotal . |
BytesPerSec |
Rate (in bytes/sec) at which the server has received the
upload. While the upload is in progress, this is an average over
the past 1-2 seconds. When the upload has finished, this is an
average over the entire upload. |
FormatRate(int rate) |
Converts a rate to more readable units. If rate is greater than 1 million, it is formatted according to the MBRateFormat resource. Otherwise, if rate is
greater than 1 thousand, it is formatted according to the KBRateFormat
resource. Otherwise, rate is formatted according of the
ByteRateFormat resource. |
FractionComplete |
A double in the range 0.0-1.0. Computed as BytesRead /BytesTotal for normal (ie non-chunked) uploads. Always 0.0 for chunked uploads. |
TimeElapsed |
TimeSpan representing the time that has elapsed since the upload began. |
TimeRemaining |
TimeSpan estimating the time left until the
upload completes. This is projected based on the elapsed time and
the fraction complete. Returns TimeSpan .MaxValue if BytesRead is 0 or if this is a chunked upload. |
FormatTimeSpan( TimeSpan ts) |
Formats a TimeSpan for readability using the following code: string format; |
Rejection |
An UploadException (eg UploadTooLargeException ) when Status is Rejected . Otherwise, null . It can be useful to display Rejection.Message to the user when a rejection occurs. NeatUpload throws an UploadTooLargeException
if the total request size is more than the size specified with the maxRequestLength
attribute of the <neatUpload>
element. The value of the UploadTooLargeException 's
Message property is based on the UploadTooLargeMessageFormat
resource in Strings.resx . |
Failure |
An Exception when Status is Failed . Otherwise, null . It can be useful to display Failure.Message to the user when a failure occurs. |
CurrentFileName |
The client-specified name of the file currently being received by the server. |
CancelVisible |
Whether the 'cancel' link should be visible. This is only 'true' when an upload is in progress and NeatUpload can use JavaScript to cancel an upload. |
StartRefreshVisible |
Whether the 'start refresh' link should be visible. This is only 'true' when the progress display is not refreshing and NeatUpload can't use JavaScript to automatically start refreshing it when the upload starts. |
StopRefreshVisible |
Whether the 'stop refresh' link should be visible. This is only 'true' when the upload is in progress, the progress display is refreshing, and NeatUpload can't use Javascript to cancel the upload. |
CancelUrl, StartRefreshUrl. StopRefreshUrl |
The URLs for the 'cancel', 'start refresh', and 'stop refresh' links, respectively. |
ProgressPage
calls it's GetResourceString()
method to retrieve all of it's resources. The default implementation uses the embedded resources compiled from Strings.resx
. If you want to use a different source, override GetResourceString()
in your subclass.
For AJAX refreshless updates to work, your ProgressPage
subclass must render as a well-formed XML document. Here are a few tips to avoid the most common problems:
<body>
' instead of '<BODY>
' or '<Body>
').
' with ' 
'.<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.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>
FilesystemUploadStorageProvider
which streams
uploaded files to a
temporary directory until you move them. If you would rather
stream uploaded files to some other location (e.g. a database, or
remote machine), or you want some other functionality not provided
by FilesystemUploadStorageProvider,
you need to
create and use a custom UploadStorageProvider
.
To create a
custom UploadStorageProvider
, create a class
which inherits from
UploadStorageProvider
. You will need
to implement the following
methods:
public abstract void Initialize(string
name, NameValueCollection attrs);
public
abstract UploadedFile CreateUploadedFile(UploadContext ctx,
string
controlUniqueID,
string
fileName,
string contentType);
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.InputFile
control, it calls the
CreateUploadedFile()
method of the provider that is configured as the defaultProvider
for the requested page. When NeatUpload calls your provider's
CreateUploadedFile()
method, it passes the UploadContext
associated with the current request, the ID of the InputFile
control
associated with the upload, along with browser-specified name and MIME
type of the file. CreateUploadedFile()
is responsible for
returning an instance of a class derived from UploadedFile
.
NeatUpload will stream the file to that object, and delegate
various InputFile
members to the associated
methods of your
UploadedFile
-derived object.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.UploadException
.
When NeatUpload detects such an exception, it:Status
to Rejected
and Rejection
to the UploadException
that occured)Application.Error
event or by
using custom error pages. UploadException
is a subclass of HttpException
, so you can
control which custom error page is used by specifying the corresponding
HTTP error code when you construct your UploadException
.UploadException
,
it assumes that it is an unexpected error, so it:Status
to Failed
and Failure
to the Exception
that occured)Application.Error
event and use custom error pages if they are configured.UploadStorageProvider
,
see FilesystemUploadStorageProvider.cs
and FilesystemUploadedFile.cs
.
For another example, see the files in the HashedInputFile
directory.Since users almost always use the file selection dialog, this issue almost never arises.
Due to a bug in IIS7's Integrated Pipeline Mode, NeatUpload will not work in IIS7's default application pool. To workaround this, you need to configure your web site to use the "Classic .NET AppPool". To encourage Microsoft to fix the bug in a future IIS release, please give 5 stars to it. For more information on the issue, please see the discussion with IIS7 Program Manager Mike Volodarsky.
When ASP.NET tracing is enabled in the application Web.config, ASP.NET reads the entire upload before
NeatUpload can access it. As a result, NeatUpload can't stream the upload
to storage and can't provide a meaningful progress bar. Since NeatUpload can't do anything useful under such
circumstances, it automatically disables itself. Specifically, it acts as though you set the
useHttpModule
attribute of the <neatUpload>
element to "false". That prevents
display of the progress bar and prevents streaming to storage, but your code should otherwise continue to function
normally.
Note that this issue only arises when you enable application-wide ASP.NET tracing (i.e. via the
Web.config). It does not arise if you only enable ASP.NET tracing at the page level using the Trace
attribute of the <%@Page%>
directive. Unfortunately, if you enable
application-wide ASP.NET tracing but disable tracing for some or all pages using the
Trace
attribute of the <%@Page%>
directive, the problem still occurs. This is
because the Trace
attribute of the <%@Page%>
directive has no effect on whether
ASP.NET reads in the entire request before NeatUpload can access it.
ProgressBar
doesn't work with web gardens/farmsNeatUpload maintains information about the upload in the application state of the web application
receiving the upload request. Since application state is not shared across instances of the web
application, the ProgressBar
will not be able to determine the state of the upload if the
requests it makes go to a different instance of the web application. This can happen if you are running a
web garden (i.e. multiple worker processes) or a web farm (i.e. multiple web servers). To ensure that you
are not running a web garden, see Microsoft's documentation on
Configuring Web Gardens with IIS 6.
For high volume sites, you might be able to use a web farm if you are able to ensure that all requests from a
particular client are routed to the same server (i.e. sticky IP).
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.
When certain 3rd-party HttpModules are used along with NeatUpload a "Data length (xxx) is shorter than Content-Length (yyy) error" can occur. To avoid this place NeatUpload's UploadHttpModule first in the <httpModules> section, before all other modules. This problem has been reported with both PageBlaster and UrlMaster. It is often encountered on sites that installed NeatUpload as part of Ultra Video Gallery (UVG).
This issue is caused by the fact that NeatUpload (and any other module that monitors upload progress) needs to intercept the request before ASP.NET reads the request body. ASP.NET needs to read the request body in order to compute certain properties of the Request object (e.g. Request.Params). So if another module accesses one of those properties before NeatUpload intercepts the request, ASP.NET will read the request body and when NeatUpload tries to read the request body, there won't be anything there.
InvalidOperationException
when using ProgressBar
with EnableViewState="false"
In order to start the progress display only if client-side validation succeeds and the form is submitted to the
server, NeatUpload needs to register an onsubmit statement that executes after other onsubmit statements such as
those that do client-side validation. Most controls (e.g. validators) register their onsubmit statements during the
PreRender
event. To ensure that NeatUpload's onsubmit statement is executed later, it needs to be
registered after PreRender
handlers have been called, but before Render()
is
called. The PreRenderComplete
event would be an good candidate, but unfortunately it is not available
in .NET 1.1. Instead, NeatUpload registers its onsubmit statement in
ProgressBar.SaveViewState()
. Unfortunately, ASP.NET does not call SaveViewState()
if EnableViewState
is "false" for the page. To avoid silent failure when viewstate is disabled,
ProgressBar.Render()
throws an InvalidOperationException
if the onsubmit
statement was not registered.
If you see the InvalidOperationException
error thrown from ProgressBar.Render()
,
the simplest workaround is to enable viewstate for the page containing the ProgressBar
. However, if that is
not desirable, you can explicitly call ProgressBar's
RegisterOnSubmitStatement()
method
after other PreRender
handlers. Under .NET 2.0 you can add it as a PreRenderComplete
event handler like this:
PreRenderComplete += new System.EventHandler(progressBar.RegisterOnSubmitStatement);Under .NET 1.1, you can override
Page.OnPreRender()
like this:protected override void OnPreRender(EventArgs e) { base.OnPreRender(e); progressBar.RegisterOnSubmitStatement(null, null); }
HttpResponse.AppendToLog()
does nothing when UploadHttpModule
is 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.
<%@ 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; } } }
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 Mac
NeatUpload overrides the form.submit()
JavaScript method to start the progress display when the
form is submitted programmatically (e.g. with a LinkButton
). Mac IE does not support overriding
form.submit()
so NeatUpload can't start the progress display if a LinkButton
is used to
submit the form. The upload still occurs though. Since Mac IE is no longer supported by Microsoft and currently
has less than 1% of the browser market, there are currently no plans to develop a workaround for this.
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.
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:
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.TestFilter.exe
program to reproduce how the UploadHttpModule
processed the request and (hopefully) determine exactly what went wrong.NeatUpload is capable of logging debug and error messages using log4net, but that capability is disabled by default to simplify installation and use of NeatUpload. To enable logging with log4net:
NeatUpload-1.1.x/log4net.config
to the root directory of your web application.Global.asax.cs
before
the namespace
keyword: [assembly:
log4net.Config.XmlConfigurator(ConfigFile="log4net.config", Watch=true)]
<assembly:
log4net.Config.XmlConfigurator(ConfigFile:="log4net.config",
Watch:=true)>