[C#]ASP.Net File Upload/Download Module Version 2

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:

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

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏