Accepting File Uploads


The FileUpload control enables users to upload files to your web application. After the file is uploaded, you can store the file anywhere you please. Normally, you store the file either on the file system or in a database. This section explores both options.

The FileUpload control supports the following properties (this is not a complete list):

  • EnabledEnables you to disable the FileUpload control.

  • FileBytesEnables you to get the uploaded file contents as a byte array.

  • FileContentEnables you to get the uploaded file contents as a stream.

  • FileNameEnables you to get the name of the file uploaded.

  • HasFileReturns true when a file has been uploaded.

  • PostedFileEnables you to get the uploaded file wrapped in the HttpPostedFile object.

The FileUpload control also supports the following methods:

  • FocusEnables you to shift the form focus to the FileUpload control.

  • SaveAsEnables you to save the uploaded file to the file system.

The FileUpload control's PostedFile property enables you to retrieve the uploaded file wrapped in an HttpPostedFile object. This object exposes additional information about the uploaded file.

The HttpPostedFile class has the following properties (this is not a complete list):

  • ContentLengthEnables you to get the size of the uploaded file in bytes.

  • ContentTypeEnables you to get the MIME type of the uploaded file.

  • FileNameEnables you to get the name of the uploaded file.

  • InputStreamEnables you to retrieve the uploaded file as a stream.

The HttpPostedFile class also supports the following method:

SaveAsEnables you to save the uploaded file to the file system.

Notice that there is some redundancy here. For example, you can get the name of the uploaded file by using either the FileUpload.FileName property or the HttpPostedFile.FileName property. You can save a file by using either the FileUpload.SaveAs() method or the HttpPostedFile.SaveAs() method.

Note

Adding a FileUpload control to a page automatically adds a enctype="multipart/form-data" attribute to the server-side <form> tag.


Saving Files to the File System

The page in Listing 4.1 illustrates how you can upload images to an application by using the FileUpload control.

Listing 4.1. FileUploadFile.aspx

[View full width]

<%@ Page Language="VB" %> <%@ Import Namespace="System.IO" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR /xhtml1/DTD/xhtml1-transitional.dtd"> <script runat="server">     Sub btnAdd_Click(ByVal sender As Object, ByVal e As EventArgs)         If (upImage.HasFile) Then             If (CheckFileType(upImage.FileName)) Then                 Dim filePath As String = "~/UploadImages/" & upImage.FileName                 upImage.SaveAs(MapPath(filePath))             End If         End If     End Sub     Function CheckFileType(ByVal fileName As String) As Boolean         Dim ext As String = Path.GetExtension(fileName)         Select Case ext.ToLower()             Case ".gif"                 Return True             Case ".png"                 Return True             Case ".jpg"                 Return True             Case ".jpeg"                 Return True             Case Else                 Return False         End Select     End Function     Sub Page_PreRender()         Dim upFolder As String = MapPath("~/UploadImages/")         Dim dir As New DirectoryInfo(upFolder)         dlstImages.DataSource = dir.GetFiles()         dlstImages.DataBind()     End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head  runat="server">     <title>FileUpload File</title> </head> <body>     <form  runat="server">     <div>     <asp:Label                  Text="Image File:"         AssociatedControl         Runat="server" />     <asp:FileUpload                  Runat="server" />     <br /><br />     <asp:Button                  Text="Add Image"         OnClick="btnAdd_Click"         Runat="server" />     <hr />     <asp:DataList                  RepeatColumns="3"         runat="server">         <ItemTemplate>         <asp:Image              ImageUrl='<%# Eval("Name", "~/UploadImages/{0}") %>'             style="width:200px"             Runat="server" />         <br />         <%# Eval("Name") %>         </ItemTemplate>     </asp:DataList>     </div>     </form> </body> </html> 

Listing 4.1 includes both a FileUpload control and a DataList control. When you upload a file, the file is saved to a folder named ImageUploads. The DataList control automatically displays the contents of the ImageUploads folder. The result is an image gallery (see Figure 4.1).

Figure 4.1. Displaying a photo gallery.


Notice that the page includes a method named CheckFileType(), which prevents users from uploading a file that does not have the .gif, .jpeg, .jpg, or .png extension. The method restricts the type of file that can be uploaded based on the file extension.

Note

The HTML 4.01 specifications define an accept attribute that you should be able to use to filter the files that can be uploaded. Unfortunately, no browser supports the accept attribute, so you must perform filtering on the server (or use some JavaScript to check the filename extension on the client).


To save a file to the file system, the Windows account associated with the ASP.NET page must have sufficient permissions to save the file. For Windows 2003 Servers, an ASP.NET page executes in the security context of the NETWORK SERVICE account. In the case of every other operating system, an ASP.NET page executes in the security context of the ASPNET account.

To enable the ASP.NET framework to save an uploaded file to a particular folder, you need to right-click the folder within Windows Explorer, select the Security tab, and provide either the NETWORK SERVICE or ASPNET account Write permissions for the folder (see Figure 4.2).

Figure 4.2. Adding Write permissions for the ASPNET account.


Saving Files to a Database

You also can use the FileUpload control to save files to a database table. Saving and retrieving files from a database can place more stress on your server. However, it does have certain advantages. First, you can avoid file system permissions issues. Second, saving files to a database enables you to more easily back up your information.

The page in Listing 4.2 enables you to save Microsoft Word documents to a database table (see Figure 4.3).

Listing 4.2. FileUploadDatabase.aspx

[View full width]

<%@ Page Language="VB" %> <%@ Import Namespace="System.IO" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR /xhtml1/DTD/xhtml1-transitional.dtd"> <script runat="server">     Sub btnAdd_Click(ByVal sender As Object, ByVal e As EventArgs)         If upFile.HasFile Then             If CheckFileType(upFile.FileName) Then                 srcFiles.Insert()             End If         End If     End Sub     Function CheckFileType(ByVal fileName As String) As Boolean         Return Path.GetExtension(fileName).ToLower() = ".doc"     End Function </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head  runat="server">     <style type="text/css">         .fileList li         {             margin-bottom:5px;         }     </style>     <title>FileUpload Database</title> </head> <body>     <form  runat="server">     <div>     <asp:Label                  Text="Word Document:"         AssociatedControl         Runat="server" />     <asp:FileUpload                  Runat="server" />     <asp:Button                  Text="Add Document"         OnClick="btnAdd_Click"         Runat="server" />     <hr />     <asp:Repeater                  DataSource         Runat="server">         <HeaderTemplate>         <ul >         </HeaderTemplate>         <ItemTemplate>         <li>         <asp:HyperLink                          Text='<%#Eval("FileName")%>'             NavigateUrl='<%#Eval("Id", "~/FileHandler.ashx?id={0}")%>'             Runat="server" />         </li>         </ItemTemplate>         <FooterTemplate>         </ul>         </FooterTemplate>     </asp:Repeater>     <asp:SqlDataSource                  ConnectionString="Server=.\SQLExpress;Integrated Security=True;             AttachDbFileName=|DataDirectory|FilesDB.mdf;User Instance=True"         SelectCommand="SELECT Id,FileName FROM Files"         InsertCommand="INSERT Files (FileName,FileBytes) VALUES (@FileName,@FileBytes)"         Runat="server">         <InsertParameters>             <asp:ControlParameter Name="FileName" Control  PropertyName="FileName" />             <asp:ControlParameter Name="FileBytes" Control  PropertyName="FileBytes" />         </InsertParameters>     </asp:SqlDataSource>     </div>     </form> </body> </html> 

Figure 4.3. Uploading Microsoft Word documents.


When you submit the form in Listing 4.2, the btnAdd_Click() method executes. This method checks the file extension to verify that the file is a Microsoft Word document. Next, the SqlDataSource control's Insert() method is called to insert the values of the FileUpload control's FileName and FileBytes properties into a local SQL Express database table. The SQL Express database table, named Files, looks like this:

Column Name

Data Type

Id

Int (IDENTITY)

FileName

NVarchar(50)

FileBytes

Varbinary(max)


The page also displays a list of the current Microsoft Word documents in the database. You can click any file and view the contents of the file. Exactly what happens when you click a file is browser (and browser settings) dependent. With Microsoft Internet Explorer, for example, the document opens directly in the browser.

Clicking the name of a document links you to a page named FileHandler.ashx. The FileHandler.ashx file is a generic HTTP Handler file. Chapter 25 discusses HTTP Handlers in detail. An HTTP Handler enables you to execute code when someone makes a request for a file with a certain path.

The FileHandler.ashx file is contained in Listing 4.3.

Listing 4.3. FileHandler.ashx

[View full width]

<%@ WebHandler Language="VB"  %> Imports System Imports System.Web Imports System.Data Imports System.Data.SqlClient Public Class FileHandler     Implements IHttpHandler     Const conString As String = "Server=.\SQLExpress;Integrated  Security=True;AttachDbFileName=|DataDirectory|FilesDB.mdf;User Instance=True"     Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler .ProcessRequest         context.Response.ContentType = "application/msword"         Dim con As SqlConnection = New SqlConnection(conString)         Dim cmd As SqlCommand = New SqlCommand("SELECT FileBytes FROM Files WHERE Id=@Id",  con)         cmd.Parameters.AddWithValue("@Id", context.Request("Id"))         Using con             con.Open()             Dim file() As Byte = CType(cmd.ExecuteScalar(), Byte())             context.Response.BinaryWrite(file)         End Using     End Sub     Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable         Get             Return False         End Get     End Property  End Class 

When the FileHandler.aspx page is requested, the ProcessRequest() method executes. This method grabs a query string item named Id and retrieves the matching record from the Files database table. The record contains the contents of a Microsoft Word document as a byte array. The byte array is sent to the browser with the Response.BinaryWrite() method.

Uploading Large Files

You must do extra work when uploading large files. You don't want to consume all your server's memory by placing the entire file in memory. When working with a large file, you need to work with the file in more manageable chunks.

First, you need to configure your application to handle large files. Two configuration settings have an effect on posting large files to the server: the httpRuntime maxRequestLength and httpRuntime requestLengthDiskThreshold settings.

The maxRequestLength setting places a limit on the largest form post that the server will accept. By default, you cannot post a form that contains more than 4MB of dataif you try, you'll get an exception. If you need to upload a file that contains more than four megabytes of data, then you need to change this setting.

The requestLengthDiskThreshold setting determines how a form post is buffered to the file system. In the previous version of ASP.NET (ASP.NET 1.1), uploading a large file could do horrible things to your server. The entire file was uploaded into the server memory. While a 10-megabyte video file was uploaded, for example, 10 megabytes of server memory was consumed.

The ASP.NET 2.0 Framework enables you to buffer large files onto the file system. When the size of the file passes the requestLengthDiskThreshold setting, the remainder of the file is buffered to the file system (in the Temporary ASP.NET Files folder).

By default, the ASP.NET framework is configured to buffer any post larger than 80KB to a file buffer. If you are not happy with this setting, then you can modify the requestLengthDiskThreshold to configure a new threshold. (The requestLengthDiskThreshold setting must be less than the maxRequestLength setting.)

The web configuration file in Listing 4.4 enables files up to 10MB to be posted. It also changes the buffering threshold to 100KB.

Listing 4.4. Web.Config

<?xml version="1.0"?> <configuration> <system.web>   <httpRuntime        maxRequestLength="10240"        requestLengthDiskThreshold="100" /> </system.web> </configuration> 

When working with large files, you must be careful about the way that you handle the file when storing or retrieving the file from a data store. For example, when saving or retrieving a file from a database table, you should never load the entire file into memory.

The page in Listing 4.5 demonstrates how you can save a large file to a database table efficiently.

Listing 4.5. FileUploadLarge.aspx

[View full width]

<%@ Page Language="VB" %> <%@ Import Namespace="System.IO" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR /xhtml1/DTD/xhtml1-transitional.dtd"> <script runat="server">     Const conString As String = "Server=.\SQLExpress;Integrated  Security=True;AttachDbFileName=|DataDirectory|FilesDB.mdf;User Instance=True"     Sub btnAdd_Click(ByVal s As Object, ByVal e As EventArgs)         If upFile.HasFile Then             If CheckFileType(upFile.FileName) Then                 AddFile(upFile.FileName, upFile.FileContent)                 rptFiles.DataBind()             End If         End If     End Sub     Function CheckFileType(ByVal fileName As String) As Boolean         Return Path.GetExtension(fileName).ToLower() = ".doc"     End Function     Sub AddFile(ByVal fileName As String, ByVal upload As Stream)         Dim con As New SqlConnection(conString)         Dim cmd As New SqlCommand("INSERT Files (FileName) Values (@FileName);SELECT  @Identity = SCOPE_IDENTITY()", con)         cmd.Parameters.AddWithValue("@FileName", fileName)         Dim idParm As SqlParameter = cmd.Parameters.Add("@Identity", SqlDbType.Int)         idParm.Direction = ParameterDirection.Output         Using con             con.Open()             cmd.ExecuteNonQuery()             Dim newFileId As Integer = CType(idParm.Value, Integer)             StoreFile(newFileId, upload, con)         End Using     End Sub     Sub StoreFile(ByVal fileId As Integer, ByVal upload As Stream, ByVal connection As  SqlConnection)         Dim bufferLen As Integer = 8040         Dim br As New BinaryReader(upload)         Dim chunk As Byte() = br.ReadBytes(bufferLen)         Dim cmd As New SqlCommand("UPDATE Files SET FileBytes=@Buffer WHERE Id=@FileId",  connection)         cmd.Parameters.AddWithValue("@FileId", fileId)         cmd.Parameters.Add("@Buffer", SqlDbType.VarBinary, bufferLen).Value = chunk         cmd.ExecuteNonQuery()         Dim cmdAppend As New SqlCommand("UPDATE Files SET FileBytes .WRITE(@Buffer, NULL,  0) WHERE Id=@FileId", connection)         cmdAppend.Parameters.AddWithValue("@FileId", fileId)         cmdAppend.Parameters.Add("@Buffer", SqlDbType.VarBinary, bufferLen)         chunk = br.ReadBytes(bufferLen)         While chunk.Length > 0             cmdAppend.Parameters("@Buffer").Value = chunk             cmdAppend.ExecuteNonQuery()             chunk = br.ReadBytes(bufferLen)         End While         br.Close()     End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head  runat="server">     <title>FileUpload Large</title> </head> <body>     <form  runat="server">     <div>     <asp:Label                  Text="Word Document:"         AssociatedControl         Runat="server" />     <asp:FileUpload                  Runat="server" />     <asp:Button                  Text="Add Document"         OnClick="btnAdd_Click"         Runat="server" />     <hr />     <asp:Repeater                  DataSource         Runat="server">         <HeaderTemplate>         <ul >         </HeaderTemplate>         <ItemTemplate>         <li>         <asp:HyperLink                          Text='<%#Eval("FileName")%>'             NavigateUrl='<%#Eval("Id", "~/FileHandlerLarge.ashx?id={0}")%>'             Runat="server" />         </li>         </ItemTemplate>         <FooterTemplate>         </ul>         </FooterTemplate>     </asp:Repeater>     <asp:SqlDataSource                  ConnectionString="Server=.\SQLExpress;Integrated Security=True;             AttachDbFileName=|DataDirectory|FilesDB.mdf;User Instance=True"         SelectCommand="SELECT Id,FileName FROM Files"         Runat="server" />     </div>     </form> </body> </html> 

In Listing 4.5, the AddFile() method is called. This method adds a new row to the Files database table that contains the filename. Next, the StoreFile() method is called. This method adds the actual bytes of the uploaded file to the database. The file contents are divided into 8040-byte chunks. Notice that the SQL UPDATE statement includes a .WRITE clause that is used when the FileBytes database column is updated.

Note

Microsoft recommends that you set the buffer size to multiples of 8040 when using the .WRITE clause to update database data.


The page in Listing 4.5 never represents the entire uploaded file in memory. The file is yanked into memory from the file system in 8040-byte chunks and fed to SQL Server in chunks.

When you click a filename, the FileHandlerLarge.ashx HTTP Handler executes. This handler retrieves the selected file from the database and sends it to the browser. The handler is contained in Listing 4.6.

Listing 4.6. FileHandlerLarge.ashx

[View full width]

<%@ WebHandler Language="VB"  %> Imports System Imports System.Web Imports System.Data imports System.Data.SqlClient Public Class FileHandlerLarge     Implements IHttpHandler     Const conString As String = "Server=.\SQLExpress;Integrated  Security=True;AttachDbFileName=|DataDirectory|FilesDB.mdf;User Instance=True"     Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest         context.Response.Buffer = False         context.Response.ContentType = "application/msword"         Dim con As New SqlConnection(conString)         Dim cmd As New SqlCommand("SELECT FileBytes FROM Files WHERE Id=@Id", con)         cmd.Parameters.AddWithValue("@Id", context.Request("Id"))         Using con             con.Open()             Dim reader As SqlDataReader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)             If reader.Read() Then                 Dim bufferSize As Integer = 8040                 Dim chunk(bufferSize - 1) As Byte                 Dim retCount As Long                 Dim startIndex As Long = 0                 retCount = reader.GetBytes(0, startIndex, chunk, 0, bufferSize)                 While retCount = bufferSize                     context.Response.BinaryWrite(chunk)                     startIndex += bufferSize                     retCount = reader.GetBytes(0, startIndex, chunk, 0, bufferSize)                 End While                 Dim actualChunk(retCount - 1) As Byte                 Buffer.BlockCopy(chunk, 0, actualChunk, 0, retCount - 1)                 context.Response.BinaryWrite(actualChunk)             End If         End Using     End Sub     Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable         Get             Return False         End Get     End Property  End Class 

The HTTP Handler in Listing 4.6 uses a SqlDataReader to retrieve a file from the database. Notice that the SqlDataReader is retrieved with a CommandBehavior.SequentialAccess parameter. This parameter enables the SqlDataReader to load data as a stream. The contents of the database column are pulled into memory in 8040-byte chunks. The chunks are written to the browser with the Response.BinaryWrite() method.

Notice that response buffering is disabled for the handler. The Response.Buffer property is set to the value False. Because buffering is disabled, the output of the handler is not buffered in server memory before being transmitted to the browser.

Warning

The method of working with large files described in this section works only with SQL Server 2005. When using earlier versions of SQL Server, you need to use the TEXTPTR() function instead of the .WRITE clause.





ASP. NET 2.0 Unleashed
ASP.NET 2.0 Unleashed
ISBN: 0672328232
EAN: 2147483647
Year: 2006
Pages: 276

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net