Introduction
Version 2 (beta 1) of the ASP.NET file upload control is now available in the downloads section of this site. Version 2 brings many enhancements over the orignal component including handlers which allow for storage and download of files using SQL Server databases.
The previous four posts have outlined how version 2 differs from the original component. This post gives the installation instructions and release notes for the beta. For details of what the component does, refer to the original article and these posts:
- ASP.Net File Upload Revisited – Part 1, IIS 7 Support
- ASP.Net File Upload Revisited – Part 2, RFC 1867 Parser
- ASP.Net File Upload Revisited – Part 3, Uploading to SQL Server
- ASP.Net File Upload Revisited – Part 4, UI and other enhancements
Installation
Installation is simple. Just copy the FileUploadLibrary.dll
file into bin folder of your web application and set up the web.config
and resources according to the instructions in the following sections. Although the library is shipped with a Visual Studio 2008 solution, it is developed for ASP.NET 2.0 and above.
Web.Config settings
Step 1 – install the HTTP module
The HTTP module intercepts incoming file upload requests and sends them off to the selected processors. For example, if you are using the SQL Server processor then the module will split the incoming request and pass each file to the SQL Server processor, which will in turn store the files in the database. To install the module, place the following code in web.config
:
1.
<
system.web
>
2.
<
httpModules
>
3.
<
add
name
=
"upload_module"
type
=
"darrenjohnstone.net.FileUpload.UploadModule, FileUploadLibrary"
/>
4.
</
httpModules
>
5.
</
system.web
>
Step 2 – install the progress handler
The progress bar gets it’s status updates from an HTTP handler which returns an XML message containing the status of the file upload. This is a light weight and efficient mechanism for getting these reports as it returns a very small message pulled directly from memory on the server. To get progress reports the HTTP handler must be installed in web.config
:
1.
<
system.web
>
2.
<
httpHandlers
>
3.
<
add
verb
=
"GET"
type
=
"darrenjohnstone.net.FileUpload.UploadProgressHandler, FileUploadLibrary"
path
=
"UploadProgress.ashx"
/>
4.
</
httpHandlers
>
5.
</
system.web
>
IIS 7 setup
To set up the module and handler for IIS 7, the handlers and module settings are required in the system.webServer
section of web.config
rather than as above:
01.
<
system.webServer
>
02.
<
modules
>
03.
<
remove
name
=
"upload_module"
/>
04.
<
add
name
=
"upload_module"
type
=
"darrenjohnstone.net.FileUpload.UploadModule, FileUploadLibrary"
/>
05.
</
modules
>
06.
<
handlers
>
07.
<
add
name
=
"UploadProgress"
verb
=
"GET"
type
=
"darrenjohnstone.net.FileUpload.UploadProgressHandler, FileUploadLibrary"
path
=
"UploadProgress.ashx"
/>
08.
</
handlers
>
09.
</
system.webServer
>
A note about request limits
ASP.Net enforces request limits on applications. These ensure that an individual request never exceeds a pre-set maximum. They exist to prevent denial of service attacks where a bad person can cause your server to go down by frequently sending requests which it can’t handle, thus forcing it to spend most of it’s resources dealing with the problem request to the exclusion of other requests. I think it’s important to mention because request limits aren’t a bad thing- they are an important security feature.
Of course request limits can prevent large files being uploaded, but they’re not the problem. ASP.Net has problems handling large files because it runs out of memory. The upload module solves this by handling memory more efficiently and dealing with files in chunks. The module will still respect the maximum request limits.
In IIS 6 the maximum request limit is set using the maxRequestLength parameter in web.config. In addition to this it is wise to set the executionTimeout parameter to prevent the process from timing out before the file is uploaded. The following example shows how these can be set to 100Mb and 1 hour respectively:
1.
<
httpRuntime
executionTimeout
=
"3600"
maxRequestLength
=
"40960"
/>
In version 1 of the module the maxRequestLength setting was completely bypassed. In hindsight I think that was probably a bad thing so in version 2 I’ve added in code to ensure that it is respected and that the request is ended if the value is exceeded.
Things are a little different in IIS 7 however. The IIS 7 request filters by default will kick in and limit the maximum content length before the module even gets a chance to do anything. To allow larger uploads we need to set the maximumAllowedContentLength in web.config by entering the statement shown below at a command prompt. This example sets the maximum content length to 100Mb for the web app called “WebApp” on the default web site.
%windir%\system32\inetsrv\appcmd set config "Default Web Site/WebApp" -section:requestFiltering -requestLimits.maxAllowedContentLength:104857600
Note that in IIS 6 the maxRequestLength is in kilobytes while in IIS 7 the maxAllowedContentLength setting is in bytes.
Selecting and configuring a processor
Two processors are provided with the upload module- SQLProcessor
and FileSystemProcessor
, these store uploaded files in a SQL Server database table or in the file system respectively. You also have the option of creating your own processors by implementing the IFileProcessor
interface.
For the upload module to work you must select and configure one processor. Selection of a processor is handled in the global.asax
file during the Application_Start
event. Here you need to set the processor type and buffer size. You then handle the ProcessorInit
event of the UploadManager
singleton class in order to set any extra properties (such as a connection string for the SQLProcessor
) in your processor when the module initialises it.
01.
void
Application_Start(
object
sender, EventArgs e)
02.
{
03.
//Uncomment one of the following lines to select a processor
04.
05.
//UploadManager.Instance.BufferSize = 1024;
06.
//UploadManager.Instance.ProcessorType = typeof(FileSystemProcessor);
07.
08.
UploadManager.Instance.ProcessorType =
typeof
(SQLProcessor);
09.
UploadManager.Instance.ProcessorInit +=
new
FileProcessorInitEventHandler(Processor_Init);
10.
}
In the Processor_Init
event handler you then set up the processor how you want it:
01.
void
Processor_Init(
object
sender, FileProcessorInitEventArgs args)
02.
{
03.
if
(args.Processor
is
FileSystemProcessor)
04.
{
05.
FileSystemProcessor processor;
06.
07.
processor = args.Processor
as
FileSystemProcessor;
08.
09.
// Set up the download path here - default to the root of the web application
10.
processor.OutputPath = @
"c:\uploads"
;
11.
}
12.
13.
if
(args.Processor
is
SQLProcessor)
14.
{
15.
SQLProcessor processor;
16.
17.
processor = args.Processor
as
SQLProcessor;
18.
19.
// Set up the connection string
20.
processor.ConnectionString =
"server=(local);initial catalog=FileUpload;integrated security=true"
;
21.
}
22.
}
If you are using the SQL Server processor then set the connection string to your database and create a new table for uploads using this script:
01.
Create
TABLE
[dbo].[UploadedFile](
02.
[Id] [
int
] IDENTITY(1,1)
NOT
NULL
,
03.
[FileName] [
varchar
](100)
NOT
NULL
,
04.
[ContentType] [
varchar
](50)
NOT
NULL
,
05.
[FileContents] [image]
NOT
NULL
,
06.
CONSTRAINT
[PK_UploadedFiles]
PRIMARY
KEY
CLUSTERED
07.
(
08.
[Id]
ASC
09.
)
WITH
(PAD_INDEX =
OFF
, STATISTICS_NORECOMPUTE =
OFF
, IGNORE_DUP_KEY =
OFF
, ALLOW_ROW_LOCKS =
ON
, ALLOW_PAGE_LOCKS =
ON
)
ON
[
PRIMARY
]
10.
)
ON
[
PRIMARY
] TEXTIMAGE_ON [
PRIMARY
]
The UploadedFile table contains an identity column which is used to provide a unique identifier to the upload.
Setting up the resources
The upload control requires a number of images, JavaScript, and CSS files in order to function. These are normally held in the upload_scripts
, upload_images
, and upload_styles
folders which are placed in the root of the web application. Get these folders from the demo application and put them into your web app. The folder structure should look similar to the following image:
If you need to change this then see the next section on configuring the controls.
Configuring the controls
Now that the HTTP module has been configured, any file uploads from standard ASP.Net file inputs will be automatically intercepted and streamed to the chosen provider. However, this is only part of the story. We can also get a progress bar which shows the percentage completed of the uploads and which file the processor is working on at any given time.
A replacement for the standard file input control has also been provided. The new DJFileUpload
control can accept multiple file inputs and offers skinning support.
Each page that is to use DJFileUpload
controls must have an instance of the DJUploadController
control at the top of the page before any upload controls. This control is responsible for emitting all of the required JavaScript and also for showing the progress bar when the form is submitted.
The DJUploadController
control has a number of properties that can be set:
Property | Description | Default value |
---|---|---|
ScriptPath | The path to the upload_scripts folder which is included in the demo project. This folder contains the scripts needed to make the UI components work. |
upload_scripts/ |
ImagePath | The path to the upload_images folder which is included in the demo project. This folder contains the images used by the upload skin along with all of the buttons. |
upload_images/ |
CSSPath | The path to the upload_styles folder which is included in the demo project. This folder contains the styles needed to skin the UI components. |
upload_styles/ |
ShowCancelButton | Set to true to show a cancel button on the progress bar. The cancel button causes the upload to be terminated when it is clicked. |
true |
ShowProgressBar | Set to true to automatically show an upload progress bar when the form is submitted. Set to false to disable the progress bar or to use the DJFileUpload control without the HTTP module. |
true |
AllowedFileExtensions | A comma separated list of file extensions to allow in the upload control (e.g. .png,.gif,.jpg ) or an empty string to allow all file extensions. |
An empty string to allow all file extensions. |
In most cases the default values of these properties will suffice. This is especially true if the upload_scripts
, upload_images
, and upload_styles
folders are placed in the root of the web application.
Once the DJUploadController
control has been added you can add as many DJFileUpload
or normal ASP.Net FileUpload
controls as required. The DJFileUpload
control has the following configuration properties.
Property | Description | Default value |
---|---|---|
InitialFileUploads | The number of file boxes to show initially in the upload control. | 1 |
MaxFileUploads | The maximum number of files to allow the user to upload via the upload control. | 5 |
ShowAddButton | true to show the add button on the control allowing users to add new file boxes. |
true |
ShowUploadButton | true to show an upload button on the control which will cause the form to be submitted. In most cases there will be a separate submit button. Any submit button will cause the upload to start. |
false |
A simple page with a single file upload control would be marked up similar to the following:
01.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="FileUploadV2._Default" %>
02.
<%@ Register assembly="FileUploadLibrary" namespace="darrenjohnstone.net.FileUpload" tagprefix="cc1" %>
03.
04.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
05.
<
html
xmlns
=
"http://www.w3.org/1999/xhtml"
>
06.
<
head
id
=
"PageHeader"
runat
=
"server"
>
07.
<
title
>File Upload Demonstration</
title
>
08.
</
head
>
09.
<
body
>
10.
<
form
id
=
"MainForm"
runat
=
"server"
>
11.
<
cc1:DJUploadController
ID
=
"DJUploadController1"
runat
=
"server"
ShowCancelButton
=
"true"
AllowedFileExtensions
=
".zip,.jpg,.png"
/>
12.
13.
<
cc1:DJFileUpload
ID
=
"DJFileUpload1"
runat
=
"server"
ShowAddButton
=
"true"
ShowUploadButton
=
"true"
/>
14.
</
form
>
15.
</
body
>
16.
</
html
>
Producing a screen like this:
Getting at the uploaded files
Once the upload has completed the Status
property of the controller can be used to retrieve a list of all files which were uploaded and any where errors were encountered. The file name is available along with any unique identifier provided by the file processor. In the event of an error the exception is provided.
01.
protected
void
Page_Load(
object
sender, EventArgs e)
02.
{
03.
if
(Page.IsPostBack && DJUploadController1.Status !=
null
)
04.
{
05.
StringBuilder sb =
new
StringBuilder();
06.
07.
sb.Append(
"<div class='up_results'>"
);
08.
sb.Append(
"<h3>Files uploaded</h3>"
);
09.
sb.Append(
"<ul>"
);
10.
11.
foreach
(UploadedFile f
in
DJUploadController1.Status.UploadedFiles)
12.
{
13.
sb.Append(
"<li>"
);
14.
sb.Append(f.FileName);
15.
16.
if
(f.Identifier !=
null
)
17.
{
18.
sb.Append(
" ID = "
);
19.
sb.Append(f.Identifier.ToString());
20.
}
21.
22.
sb.Append(
"</li>"
);
23.
}
24.
25.
sb.Append(
"</ul>"
);
26.
27.
sb.Append(
"<h3>Files with errors</h3>"
);
28.
sb.Append(
"<ul>"
);
29.
30.
foreach
(UploadedFile f
in
DJUploadController1.Status.ErrorFiles)
31.
{
32.
sb.Append(
"<li>"
);
33.
sb.Append(f.FileName);
34.
35.
if
(f.Identifier !=
null
)
36.
{
37.
sb.Append(
" ID = "
);
38.
sb.Append(f.Identifier.ToString());
39.
}
40.
41.
if
(f.Exception !=
null
)
42.
{
43.
sb.Append(
" Exception = "
);
44.
sb.Append(f.Exception.Message);
45.
}
46.
47.
sb.Append(
"</li>"
);
48.
}
49.
50.
sb.Append(
"</ul>"
);
51.
sb.Append(
"</div>"
);
52.
ltResults.Text = sb.ToString();
53.
}
54.
}
In the case of the SQLProcessor
the Status.Identifier
property will be set to the Id of the item in the database.
Downloading from SQL Server
If you are using the SQLProcessor
to upload files into SQL Server, then I’ve also provided code in that class for downloading the files as a stream. The SQLFileProcessor
class has two extra methods not shared with other IFileProcessor
implementations.
01.
/// <summary>
02.
/// Gets the file name and content type of the file.
03.
/// </summary>
04.
/// <param name="id">The ID of the file to get.</param>
05.
/// <param name="fileName">File name.</param>
06.
/// <param name="contentType">Content type.</param>
07.
/// <returns>True if the file is found, otherwise false.</returns>
08.
public
virtual
bool
GetFileDetails(
int
id,
out
string
fileName,
out
string
contentType)
09.
{
10.
....
11.
}
12.
13.
/// <summary>
14.
/// Gets the file from the database and writes it to a stream.
15.
/// </summary>
16.
/// <param name="stream">Stream to write to.</param>
17.
/// <param name="id">The id of the record to get.</param>
18.
/// <param name="blockSize">The size of blocks to stream the data in.</param>
19.
public
virtual
void
SaveFileToStream(Stream stream,
int
id,
int
blockSize)
20.
{
21.
....
22.
}
The GetFileDetails
method is used to get the name and content type of a file from the database with the given ID whilst the SaveFileToStream
method is used to get the blob data of the stored file and write it to a stream in chunks. Together these methods allow files to be retrieved from the database and manipulated or downloaded. A good example of this is the SQLFileDownloadHandler
class which implements a simple HTTP handler allowing a file to be downloaded. The relatively simple code for the handler is shown below:
01.
/// <summary>
02.
/// An HTTP handler which allows files to be downloaded from a SQL database.
03.
/// </summary>
04.
public
class
SQLFileDownloadHandler : IHttpHandler
05.
{
06.
SQLProcessor _processor;
07.
08.
#region Constructor
09.
10.
/// <summary>
11.
/// Initializes a new instance of the <see cref="SQLFileDownloadHandler"/> class.
12.
/// </summary>
13.
public
SQLFileDownloadHandler()
14.
{
15.
_processor = UploadManager.Instance.GetProcessor()
as
SQLProcessor;
16.
17.
if
(_processor ==
null
)
18.
{
19.
throw
new
Exception(
"The processor must be of type SQLProcessor for downloads."
);
20.
}
21.
}
22.
23.
#endregion
24.
25.
#region IHttpHandler Members
26.
27.
/// <summary>
28.
/// Gets a value indicating whether another request can use the <see cref="T:System.Web.IHttpHandler"/> instance.
29.
/// </summary>
30.
/// <value></value>
31.
/// <returns>true if the <see cref="T:System.Web.IHttpHandler"/> instance is reusable; otherwise, false.</returns>
32.
public
bool
IsReusable
33.
{
34.
get
35.
{
36.
return
false
;
37.
}
38.
}
39.
40.
/// <summary>
41.
/// Enables processing of HTTP Web requests by a custom HttpHandler that implements the <see cref="T:System.Web.IHttpHandler"/> interface.
42.
/// </summary>
43.
/// <param name="context">An <see cref="T:System.Web.HttpContext"/> object that provides references to the intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests.</param>
44.
public
void
ProcessRequest(HttpContext context)
45.
{
46.
int
id;
47.
string
contentType;
48.
string
fileName;
49.
50.
if
(
int
.TryParse(context.Request[
"id"
],
out
id))
51.
{
52.
if
(_processor.GetFileDetails(id,
out
fileName,
out
contentType))
53.
{
54.
context.Response.ContentType = contentType;
55.
56.
if
(context.Request[
"attach"
] ==
"yes"
)
57.
{
58.
context.Response.AddHeader(
"Content-Disposition"
,
"attachment; filename=\""
+ fileName +
"\""
);
59.
}
60.
61.
_processor.SaveFileToStream(context.Response.OutputStream, id, UploadManager.Instance.BufferSize);
62.
context.Response.Flush();
63.
}
64.
}
65.
}
66.
67.
#endregion
68.
}
In this case the Id of the file is read from a corresponding URL parameter. A second URL parameter (attach) simply causes the file name and content disposition to be configured for an attachment when it is set to “yes”. This simply causes the browser to initiate a file download rather than displaying the file inline as it would for an image.
If you want to use the download handler in you applications you need to add it to the web.config
just like the others:
1.
<
httpHandlers
>
2.
<
add
verb
=
"GET"
type
=
"darrenjohnstone.net.FileUpload.SQLFileDownloadHandler, FileUploadLibrary"
path
=
"DownloadFile.ashx"
/>
3.
</
httpHandlers
>
and for IIS 7
1.
<
system.webServer
>
2.
<
handlers
>
3.
<
add
name
=
"FileDownload"
verb
=
"GET"
type
=
"darrenjohnstone.net.FileUpload.SQLFileDownloadHandler, FileUploadLibrary"
path
=
"DownloadFile.ashx"
/>
4.
</
handlers
>
5.
</
system.webServer
>
Once this is done simply use a hyperlink to download the file:
1.
<
a
href
=
"DownloadFile.ashx?ID=1&attach=yes"
>Download the file</
a
>
Creating a custom processor
You can create your own custom processor by implementing the IFileProcessor
interface in a custom class. For a good example of this read about how the SQLProcessor
was implemented in this previous post: ASP.Net File Upload Revisited – Part 3, Uploading to SQL Server.
Component dependencies (or rather not)
Whilst the HTTP module and UI components are designed to work together, there is no requirement for this. The HTTP module will intercept all file uploads, including those from standard ASP.Net FileUpload
controls. There is no need to use the DJFileUpload
control if it is not required.
Conversely, the DJFileUpload
control can be used without the HTTP module as a direct replacement for the ASP.Net FileUpload
control if all that is required is skinning support or file extension filtering. To do this, the ShowProgressBar
property of the DJUploadController
control on the page must be set to false
.
Object model reference
For the techies out there, here is the current object model of the library (click to make it bigger). This might help to make some of the previous explanations clearer. In addition to this, the previous four posts also give a great deal more technical information.