Pages

Advertisement

Thursday, August 16, 2007

File Uploading Using ASP.NET

In the golden olden days of ASP, managing a file upload was pretty difficult. Most developers reverted to digging deep in their wallets to purchase a third-party add-on to help them achieve the desired result. No longer.

With ASP.NET, you can now upload files with practically a few lines of code. And the following four easy-to-follow steps show you exactly how to do it.

  1. Add a File Field control to your form. You'll find this under the HTML tab on the toolbox. You'll have seen this control when uploading attachments through Hotmail, or when sending files to a Web site.
  2. Right-click on the File Field control and check the Run as Server Control option. This allows you to manipulate the control in code, sort of like a less-functional ASP.NET server control.
  3. Change the ID of your File Field control to something more understandable, such as "fileUpload".
  4. Enter the HTML view of your Web form and find the opening <form> tag. You'll need to edit this to add the parameter encType="multipart/form-data". Your <form> tag may look something like this when you're finished:

<form id="Form1" method="post" encType="multipart/form-data" 
   runat="server">


And that's it. You've set up your form to receive file uploads. But after the user has selected a file and submitted your form, how do you manipulate the sent file? The easiest technique is to run a simple line of code, like this:

NameOfFileFieldElement.PostedFile.SaveAs( _
Server.MapPath("uploadedfile.txt"))



Pretty simple, really. You might also want to check that the user has uploaded a valid file first, before saving (unless you're really into errors). The following function does this for you, checking for null uploads and zero byte files: Public Function FileFieldSelected(ByVal FileField As _
  System.Web.UI.HtmlControls.HtmlInputFile) As Boolean
    ' Returns a True if the passed
    ' FileField has had a user post a file
    If FileField.PostedFile Is Nothing Then Return False
    If FileField.PostedFile.ContentLength = 0 Then Return False
    Return True
End Function
TOP TIP
If you get an "access denied" error when trying to save files directly to your Web application folder, go check your permissions. Ensure that your virtual directory in IIS has read and write permissions. (Change this through IIS.) You may also want to ensure that your ASPNET, guest, or impersonated accounts have appropriate permissions, both for computer access and for the actual folder itself. (To change this, right-click on the folder, select Sharing and Security, and then select the Security tab.)

The problem is that both you and I know that 95% of people reading this don't really want to go ahead and store files directly on the server file system. Rather, you want to save information into your database, into that SQL Server "image" field.

Every article I've seen so far manages to conveniently skip this topic. But not this one...


Figure: Click on the button and pick a file!

Storing Uploaded Files in Your Database

Firstly, a few tips on storing files inside your SQL Server database.

For convenience, you'll really need to store at least three bits of information about your file to get it out in the same shape as you put it in. I'd suggest "data" (a field that will hold your actual file as a byte array, data type "image"), "type" (a field to hold details of the type of file it is, data type "varchar"), and "length" (a field to hold the length in bytes of your file, data type "int").

I'd also recommend "downloadName", a field to hold the name that the file had when it was uploaded, data type "varchar". This helps suggest a name should the file be downloaded again via the Web.

The problem you have is translating the information from the File Field control into an acceptable format for your database. For a start, you need to get your file into a byte array to store it in an image field. You also need to extract the file type, length, and the download name. Once you have this, set your fields to these values using regular ADO.NET code.

So, how do you get this information? It's simple: just use the following ready-to-run code snippets, passing in your File Field control as an argument. Each function will return just the information you want to feed straight into your database, from a byte array for the image field to a string for the file type.


Public Function GetByteArrayFromFileField( _
  ByVal FileField As System.Web.UI.HtmlControls.HtmlInputFile) _
  As Byte()
  ' Returns a byte array from the passed 
  ' file field controls file
  Dim intFileLength As Integer, bytData() As Byte
  Dim objStream As System.IO.Stream
  If FileFieldSelected(FileField) Then
      intFileLength = FileField.PostedFile.ContentLength
      ReDim bytData(intFileLength)
      objStream = FileField.PostedFile.InputStream
      objStream.Read(bytData, 0, intFileLength)
      Return bytData
  End If
End Function
 
Public Function FileFieldType(ByVal FileField As _
  System.Web.UI.HtmlControls.HtmlInputFile) As String
    ' Returns the type of the posted file
    If Not FileField.PostedFile Is Nothing Then _
      Return FileField.PostedFile.ContentType
End Function
 
Public Function FileFieldLength(ByVal FileField As _
  System.Web.UI.HtmlControls.HtmlInputFile) As Integer
    ' Returns the length of the posted file
    If Not FileField.PostedFile Is Nothing Then _
      Return FileField.PostedFile.ContentLength
End Function
 
Public Function FileFieldFilename(ByVal FileField As _
  System.Web.UI.HtmlControls.HtmlInputFile) As String
    ' Returns the core filename of the posted file
    If Not FileField.PostedFile Is Nothing Then _
      Return Replace(FileField.PostedFile.FileName, _
      StrReverse(Mid(StrReverse(FileField.PostedFile.FileName), _
      InStr(1, StrReverse(FileField.PostedFile.FileName), "\"))), "")
End Function
Sorted! One question remains, however. Once you've got a file inside a database, how do you serve it back up to a user? First, get the data back out of SQL Server using regular ADO.NET code. After that? Well, here's a handy function that'll do all the hard work for you. Simply pass it the data from your table fields and hey presto: 
 
Public Sub DeliverFile(ByVal Page As System.Web.UI.Page, _
  ByVal Data() As Byte, ByVal Type As String, _
  ByVal Length As Integer, _
  Optional ByVal DownloadFileName As String = "")
    ' Delivers a file, such as an image or PDF file,
    ' back through the Response object
    ' Sample usage from within an ASP.NET page:
    ' - DeliverFile(Me, bytFile(), strType, intLength, "MyImage.bmp")
    With Page.Response
      .Clear()
      .ContentType = Type
      If DownloadFileName <> "" Then
          Page.Response.AddHeader("content-disposition", _
            "filename=" & DownloadFileName)
      End If
      .OutputStream.Write(Data, 0, Length)
      .End()
    End With
End Sub

Simply pass it your byte array, file type, and length, and it'll send it straight down to your surfer. If it's an image, it'll be displayed in the browser window. If it's a regular file, you'll be prompted for download.

If it's made available for download, this function also allows you to specify a suggested download file name, a technique that many ASP.NET developers spend weeks trying to figure out. Easy!

Using Visual Basic .NET to Upload a File to a Web Server in ASP.NET

his step-by-step article describes how to upload a file to a Web server by using Visual Basic .NET. In this article, you create an ASP.NET file (WebForm1.aspx) and its related code-behind file (WebForm1.aspx.vb) to upload files to a directory that is named Data.

Create the ASP.NET Application

In Microsoft Visual Studio .NET, follow these steps to create a new application to upload files to the Web server:

  1. Start Microsoft Visual Studio .NET.
  2. On the File menu, point to New, and then click Project.
  3. In the New Project dialog box, click Visual Basic Projects under Project Types, and then click ASP.NET Web Application under Templates.
  4. In the Location box, type the URL to create the project. For this example, type http://localhost/VBNetUpload, which creates the default project name of VBNetUpload. Notice that the WebForm1.aspx file loads in the Designer view of Visual Studio .NET.
Create the Data Directory

After you create the application, you create the Data directory that will accept uploaded files. After you create this directory, you must also set write permissions for the ASPNET worker account.

  1. In the Solution Explorer window of Visual Studio .NET, right-click VBNetUpload, point to Add, and then click New Folder. By default, a new folder that is named NewFolder1 is created.
  2. To change the folder name to Data, right-click NewFolder1, click Rename, and then type Data.
  3. Start Windows Explorer, and then locate the Data file system folder that you created in Step 2. By default, this folder is located in the following folder:
    C:\Inetpub\wwwroot\VBNetUpload\Data

  4. To change the security settings to grant write permissions to the Data directory, right-click Data, and then click Properties.
  5. In the Data Properties dialog box, click the Security tab, and then click Add.
  6. In the Select Users or Groups dialog box, click the ASPNET account, and then click Add. Click OK to close the Select Users or Groups dialog box.
  7. Click the aspnet_wp account (computername\ASPNET) account, and then click to select the Allow check boxes for the following permissions:

    Click to clear any other Allow and Deny check boxes.


  8. Click OK to close the Data Properties dialog box. You have successfully modified the Data directory permissions to accept user-uploaded files.

Modify the WebForm1.aspx Page

To modify the HTML code of the WebForm1.aspx file to permit users to upload files, follow these steps:


  1. Return to the open instance of Visual Studio .NET. WebForm1.aspx should be open in the Designer window.
  2. To view the HTML source of the WebForm1.aspx page, right-click WebForm1.aspx in the Designer window, and then click View HTML Source.
  3. Locate the following HTML code, which contains the <form> tag:
    <form id="Form1" method="post" runat="server">

  4. Add the enctype="multipart/form-data" name-value attribute to the <form> tag as follows:
    <form id="Form1" method="post"
    enctype="multipart/form-data" runat="server">

  5. After the opening <form> tag, add the following code:
    <INPUT type=file id=File1 name=File1 runat="server" />
    <br>
    <input type="submit" id="Submit1" value="Upload"
    runat="server" />

  6. Verify that the HTML <form> tag appears as follows:
    <form id="Form1" method="post" enctype="multipart/form-data"
    runat="server">
    <INPUT type=file id=File1 name=File1 runat="server" />
    <br>
    <input type="submit" id="Submit1" value="Upload"
    runat="server" />
    </form>

Add the Upload Code to the WebForm1.aspx.vb Code-Behind File

To modify the WebForm1.aspx.vb code-behind file so that it accepts the uploaded data, follow these steps:


  1. On the View menu, click Design.
  2. Double-click Upload. Visual Studio opens the WebForm1.aspx.vb code-behind file and automatically generates the following method code:

    Private Sub Submit1_ServerClick(ByVal sender _
                                    As System.Object, _
                                    ByVal e As System.EventArgs) _
                                    Handles Submit1.ServerClick
     
    End Sub

  3. Verify that the following code exists at the class level of the WebForm1.vb file:


  4.  
    Protected WithEvents Submit1 _
              As System.Web.UI.HtmlControls.HtmlInputButton
    Protected WithEvents File1 _
              As System.Web.UI.HtmlControls.HtmlInputFile
    If this code does not exist in the file, add the code into the file after the following line:
     
    Inherits System.Web.UI.Page

  5. Locate the following code:

    Private Sub Submit1_ServerClick(ByVal sender _
                                    As System.Object, _
                                    ByVal e As System.EventArgs) _
                                    Handles Submit1.ServerClick

  6. Press ENTER to add a blank line, and then add the following code:

    If Not File1.PostedFile Is Nothing And _
           File1.PostedFile.ContentLength > 0 Then
     
    Dim fn As String = System.IO.Path.GetFileName(File1. _
                                                  PostedFile. _
                                                  FileName)
    Dim SaveLocation as String = Server.MapPath("Data") & "\" & fn
    Try
      File1.PostedFile.SaveAs(SaveLocation)
      Response.Write("The file has been uploaded.")
    Catch Exc As Exception
      Response.Write("Error: " & Exc.Message)
    End Try
    Else
    Response.Write("Please select a file to upload.")
    End If

    This code first verifies that a file has been uploaded. If no file was selected, you receive the "Please select a file to upload" message. If a valid file is uploaded, its file name is extracted by using the System.IO namespace, and its destination is assembled in a SaveAs path. After the final destination is known, the file is saved by using the File1.PostedFile.SaveAs method. Any exception is trapped, and the exception message is displayed on the screen.


  7. Verify that the Submit1 subroutine appears as follows:

    Private Sub Submit1_ServerClick(ByVal sender _
                                    As System.Object, _
                                    ByVal e As System.EventArgs) _
                                    Handles Submit1.ServerClick
    If Not File1.PostedFile Is Nothing And _
           File1.PostedFile.ContentLength > 0 Then
      Dim fn As String = _
          System.IO.Path.GetFileName(File1.PostedFile.FileName)
      Dim SaveLocation as String = _
          Server.MapPath("Data") & "\" & fn
      Try
        File1.PostedFile.SaveAs(SaveLocation)
        Response.Write("The file has been uploaded.")
      Catch Exc As Exception
        Response.Write("Error: " & Exc.Message)
      End Try
      Else
        Response.Write("Please select a file to upload.")
      End If
    End Sub

Test the Application

To build your Visual Studio .NET solution and to test the application, follow these steps:


  1. On the Build menu, click Build Solution.
  2. In Solution Explorer, right-click WebForm1.aspx, and then click View in Browser.
  3. After WebForm1.aspx opens in the browser, click Browse.
  4. In the Choose File dialog box, select a file that is smaller than 4 megabytes (MB), and then click Open.
  5. To upload the file, click Upload. Notice that the file uploads to the Web server and that you receive the "The file has been uploaded" message.
  6. Return to the open instance of Windows Explorer, and then locate the Data directory.
  7. Verify that the file has been uploaded to the Data directory.

Upload Larger Files

By default, ASP.NET permits only files that are 4,096 kilobytes (KB) (or 4 megabytes [MB]) or less to be uploaded to the Web server. To upload larger files, you must change the maxRequestLength parameter of the <httpRuntime> section in the Web.config file.

Note: When the maxRequestLength attribute is set in the Machine.config file and then a request is posted (for example, a file upload) that exceeds the value of maxRequestLength, a custom error page cannot be displayed. Instead, Microsoft Internet Explorer will display a "Cannot find server or DNS" error message.

If you want to change this setting for all of the computer and not just this ASP.NET application, you must modify the Machine.config file.

By default, the <httpRuntime> element is set to the following parameters in the Machine.config file:


<httpRuntime
executionTimeout="90"
maxRequestLength="4096"
useFullyQualifiedRedirectUrl="false"
minFreeThreads="8"
minLocalRequestFreeThreads="4"
appRequestQueueLimit="100"
/>

The Machine.config file is located in the \System Root\Microsoft.NET\Framework\Version Number\Config folder.

Complete Code Listing

WebForm1.aspx


<%@ Page Language="vb" AutoEventWireup="false"
                       Codebehind="WebForm1.aspx.vb"
                       Inherits="VBNetUpload.WebForm1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
  <HEAD>
    <title>WebForm1</title>
    <meta name="GENERATOR"
          content="Microsoft Visual Studio.NET 7.0">
    <meta name="CODE_LANGUAGE" content="Visual Basic 7.0">
    <meta name=vs_defaultClientScript content="JavaScript">
    <meta name=vs_targetSchema
          content="http://schemas.microsoft.com/intellisense/ie5">
  </HEAD>
  <body MS_POSITIONING="GridLayout">
 
    <form id="Form1" enctype="multipart/form-data" method="post"
          runat="server">
 
<INPUT type=file id=File1 name=File1 runat="server" >
<br>
<input type="submit" id="Submit1" value="Upload" runat="server"
       NAME="Submit1">
 
 
    </form>
 
  </body>
</HTML>
WebForm1.aspx.vb
 
Public Class WebForm1
    Inherits System.Web.UI.Page
    Protected WithEvents File1 _
              As System.Web.UI.HtmlControls.HtmlInputFile
    Protected WithEvents Submit1 _
              As System.Web.UI.HtmlControls.HtmlInputButton
 
#Region " Web Form Designer Generated Code "
 
    'This call is required by the Web Form Designer.
    <System.Diagnostics.DebuggerStepThrough()> _
    Private Sub InitializeComponent()
 
    End Sub
 
    Private Sub Page_Init(ByVal sender As System.Object, _
                          ByVal e As System.EventArgs) _
                          Handles MyBase.Init
        'CODEGEN: This method call is required by the Web Form
        'Designer
        'Do not modify it using the code editor.
        InitializeComponent()
    End Sub
 
#End Region
 
    Private Sub Page_Load(ByVal sender As System.Object, _
                          ByVal e As System.EventArgs) _
                          Handles MyBase.Load
        'Put user code to initialize the page here
    End Sub
 
    Private Sub Submit1_ServerClick(ByVal sender As System.Object, _
                                    ByVal e As System.EventArgs) _
                                    Handles Submit1.ServerClick
 
        If Not File1.PostedFile Is Nothing And _
               File1.PostedFile.ContentLength > 0 Then
            Dim fn As String = _
               System.IO.Path.GetFileName(File1.PostedFile.FileName)
            Dim SaveLocation as String = Server.MapPath("Data") _
                & "\" & fn
            Try
                File1.PostedFile.SaveAs(SaveLocation)
                Response.Write("The file has been uploaded.")
            Catch Exc As Exception
                Response.Write("Error: " & Exc.Message)
            End Try
        Else
            Response.Write("Please select a file to upload.")
        End If
 
    End Sub
End Class

More Information

Theoretically, the maximum file upload size is fairly large. However, because of ASP.NET health monitoring, you cannot upload very large files in ASP.NET. The ASP.NET worker process has a virtual address space of 2 gigabytes (GB). However, the ASP.NET worker process only uses a little more than 1 GB because of health monitoring and memory fragmentation.

During the upload process, ASP.NET loads the whole file in memory before the user can save the file to the disk. Therefore, the process may recycle because of the memoryLimit attribute of the processModel tag in the Machine.config file. The memoryLimit attribute specifies the percentage of physical memory that the ASP.NET worker process can exhaust before the process is automatically recycled. Recycling prevents memory leaks from causing ASP.NET to crash or to stop responding.

Additionally, other factors play a role in the maximum file size that can be uploaded. These factors include available memory, available hard disk space, processor speed, and current network traffic. With regular traffic of files being uploaded, Microsoft recommends that you use a maximum file size in the range of 10 to 20 megabytes (MB). If you rarely upload files, the maximum file size may be 100 MB.

Note: You can upload files that are larger than 100 MB in ASP.NET. However, Microsoft recommends that you follow the maximum file upload sizes that are mentioned in this article. To determine more precise file sizes, perform stress testing on computers that are similar to the ones that will be used in production.

You may notice the following error messages if you encounter file size limits during the file upload process:


In the event log, the error message will be similar to the following:

aspnet_wp.exe (PID:PIDNumber) was recycled because memory
consumption exceeded the SizeLimit MB (Percentage percent of
available RAM).

  • Exception of type System.OutOfMemoryException was thrown.

    You may also find that uploads occur very slowly. If you watch the Aspnet_wp.exe process in Windows Task Manager, you will notice that the memory delta changes by 64 KB every 1 to 2 seconds. Depending on the size of the file, this delay may cause the ASP.NET worker process to recycle because of a responseDeadlock error.