Uploading and Downloading Files

A lot of applications require information to be transferred in the form of data files. For example, a prospective employee may want to upload a CV in Microsoft Word format to an employment agency's website. Likewise, an employer may want to download CVs of prospective employees. There are lots of applications potentially requiring file transfer; from software download to image bureaux.

File upload can be achieved in a straightforward way by using a FileUpload control. This control only requires a small amount of code to cause an uploaded file to be stored on the file system of the server. However, it is sometimes preferable to store uploaded files in a database. This is more complex, but both techniques will be presented below, together with a mechanism to download a file which may or may not have been previously uploaded.

Uploading using a folder on the server

For this example we will assume there is a folder in the home directory for the application called 'upload'.

  1. Close any open project and create a new C# ASP.NET Website called Upload
  2. In Solution Explorer create a new folder called Upload in the application directory.
  3. Drag a FileUpload control onto the default web page.
  4. Drag a Button onto the web page and change its label to 'Upload File'.
  5. Double click the button, to bring up the code window with an empty button click event handler for the button, as follows:
    protected void Button1_Click(object sender, EventArgs e)
    {
    
    }
    
  6. We need code to check that a file has been selected by the user, and if so code to accept the file and store it in the Upload folder. The FileUpload control has several properties and methods which allow us to do this quite easily. FileUpload has a property HasFile which returns true if a file is available. The SaveAs method simply needs the full path for the file when it is placed on the server. In this case we need to make up the file path by adding the bare filename of the file to the actual location of the Upload folder. The bare filename is given by the FileName property of the FileUpload control. There is a predefined method called Server.MapPath which will return an actual path if you give it a URL on the server. In this case the URL is '~/Upload/'. Note: we have used the '~' symbol before to denote the home folder for the application. The complete code is:
    protected void Button1_Click(object sender, EventArgs e)
    {
      if (FileUpload1.HasFile)
        FileUpload1.SaveAs
            (Server.MapPath("~/Upload/") + FileUpload1.FileName);
    }
    

These two lines of code perform the complete file upload process.

Security notes

You may think there are security issues with this scenario, and you are right. However, all is not as straightforward as it may seem. The end user of your site only has permission to view your pages, so you may ask how they can save a file on the server. The answer is that it is not the end user saving the file it is the ASP.NET server process. Security is achieved by making sure that:

  • The server process has permissions to save files in the target folder.
  • The application doesn't allow the end user to choose arbitrary locations to save the uploaded file.
  • The end user doesn't have permissions to execute the uploaded file on the server using web based access.
  • Restricting the types of file that can be uploaded.

The above points are addressed by the web or network administrator who performs the configuration of the application.

Downloading a file

The simplest form of download uses exactly the same mechanism as a hypertext link. In our page we can include a link, whose URL is the name of the file. This can be created dynamically from a list of files in a folder.

However, the problem with this solution is that the file may automatically open in the user's browser, replacing the current page. Files which are not recognised by the browser (e.g. .EXE files) will cause the browser to prompt the user to Open/Run or Save the file, as follows:

File Download - Security Warning 

An example of the simple download is given in the ZIP version of Download.

A more controlled solution to the download problem forces the browser to display a file download security warning and provides the filename ready to save the file to your local file system.

When you retrieve a document from the Internet it is packaged in an HTTP Response packet. The packet has a header which specifies the content type. This will usually be an HTML file or an image such as a GIF or JPG file. Content types are specified using a naming scheme called MIME (Multi-purpose Internet Mail Extensions) which was originally created for transfer of files using email. A MIME type has two parts separated by a solidus ('/'). The first part is a generic type, the second part is the specific variant. An HTML page has MIME type 'text/html'. A .jpg image has MIME type 'image/jpeg' and a .gif image has type 'image/gif'.

If we want to allow a user to save a file that is being downloaded we need to return the file with a header that tells the users browser that it is a data file and shouldn't be displayed. The usual MIME type for this is 'application/octet-stream'. This signals the browser that binary data is being sent. The browser will respond by asking the user if they want to save the file or open/run it. You can also send a header as part of the HTTP response which tells the browser that a file is being sent and also gives its name. This header type is named 'Content-Disposition'. Assume we have a webpage with a TextBox (txtFilename), a Button (butDownload) and a Label (labMessage). The user can type a filename in the TextBox (not very realistic), and when the Button is clicked the file will be downloaded. The code to handle this scenario looks like this:

protected void butDownload_Click(object sender, EventArgs e)
{
  if (System.IO.File.Exists
       (Server.MapPath("~/Upload/") + txtFilename.Text))
  {
    Response.Clear();
    Response.ContentType = "application/octet-stream";
    Response.AddHeader
       ("Content-Disposition",
        "attachment; filename=\"" + txtFilename.Text + "\"");
    Response.Flush();
    Response.WriteFile
       (Server.MapPath("~/Upload/") + txtFilename.Text);
    labMessage.Text = "";
  }
  else
  {
    Message.Text = "File does not exist";
  }
}

You can download a ZIP version of the ForceDownload application .

The code to fill a list box with the names of files is relatively straightforward. The following code shows how to list all .jpg files from the 'upload' folder. The method Path.GetFiles() returns an array of strings containing the fully qualified filename (including drive letter and folder names) for each of the matching files.

  foreach (string fname in
    Directory.GetFiles(Server.MapPath("~/upload"),
                       "*.jpg", 
                       SearchOption.TopDirectoryOnly))
      {
        ListBox1.Items.Add(Path.GetFileName(fname));
      }

Note: if you want to allow the end user to select a file in the list a press abutton to down load there is one thing to watch for. You must make sure the listbox gets filled with the filenames just once (not every time the page loads) otherwise the page can't remember which file was selected. You can download a ZIP version of the ListBoxDownload application which uses this technique.

Uploading with a database

The previous sections have ignored the problems of keeping track of files which have been uploaded, and of giving the user some indication of which files are available for download in a dynamic way. A more realistic scenario would mean that uploaded files should be logged in a database, so that the current list can be made available to the user. There, is still an important decision to be made, and that is whether to save the uploaded file in the database, or whether to simply save a reference to the file in the database and store the files in a separate folder. Both possibilities have advantages and disadvantages, but both are still viable. The choice probably depends on circumstances.

Database requirements - File saved as part of record

The file will be saved as in a field as a binary image, so we will also need to keep track of other information about the file. This will be the filename, its content-type, and the size of the file. We cannot use the filename as the key field, as there is a good chance of files being uploaded with the same filename, so we should use an autonumber field (identity field in MS SQL) to avoid duplicate keys. If filenames are duplicated, we may also want to store further information about the file. For example, an employment agency may allow CVs to be uploaded, so their may be a field identifying the employee. Another example might be an image repository where there may need to be fields for the identity of the photographer, a description of the image and the date taken.

Database requirements - File saved separate from the database

In this scenario we will need a unique key for each record, which again could be done with an autonumber field. However, Since the files are being saved independently of the database we will need to use a unique filename to save each file. A simple solution to this is to store the file using the key value as its name, and store the original filename as part of the record. We still need to have fields for 'content-type' and file size. We may also want identification information for the 'owner' of the file and other descriptive information. 

Comparison

The only real difference between the table structures for the scenarios is the presence/absence of a field for the file data. However, the separate file solution poses a problem when implementing the code to save the record and save the file as we shall see later.

Uploading and downloading files saved in the database.

Before looking at the process in detail, let us look at the solution from the point of view of the design and development process.

  • Web form design: a simple interface is all that is required for file upload (FileUpload control and an Upload button), and all the work will be done within the click event handler for the Upload button. However, file download ought to be made convenient for the user by providing a list of files for download. This can be done by having a GridView control listing the records in the database. We will need to trim the GridView so that it doesn't display the FileData column as it is not a simple field (e.g. text or numeric). We can enable the Select command on the GridView. If we change the text from 'Select' to 'Download' this provides a simple interface to allow the user to select and download a file for download in one action. We can also enable the Edit, Paging and Sorting commands to maximize the usefulness of the interface.
  • Script code: We only have two actions to code on the web form. These are the Upload File button and the SelectedIndexChanged action on the GridView.
  • Upload Code: We must connect to the database, retrieve the filename, content type, file size and file data from the upload control. Then we create a parameterised SQL insert command for creating a new record in the database. We set the values of the parameters and execute the command. Finally we must refresh the GridView control so that it displays the grid with the newly inserted record.
  • Download code: We must get the filename from the selected row of the GridView. We can then connect to the database, build an SQL select statement to retrieve the record corresponding to the selected filename. We execute the select statement and retrieve the values, including the file data from the retrieved record. Finally we can send the response to the client by creating a new response packet, much like the non-database version of the file upload/download example.

Step by step

  1. Create a new C# ASP.NET Website called UploadDB.
  2. Create a new database, called Uploads.mdf with a table called tblFileData, matching the following structure:
    Upload table layout
    Make sure Filename and ContentType are large enough to hold meaningful data (minimum of 50 characters), and note the Identity properties for the FileId field.
  3. Build a user interface matching the following display, noting that you may initially have four columns displayed in your GridView, and Download links may read 'Select':
    Upload form layout
  4. If you have got four columns (FileData is displayed) you will need to amend the properties of the GridView to hide the FileData column. Use the SmartTag dialog and choose Edit Columns
    Upload Fields Editor
  5. In the Selected Fields section choose FileData. In the Bound Field Properties section set the Visible property to False.
  6. If the GridView has 'Select' links you will need to change the text to 'Download'. Using the Edit Columns dialog (from stage 4) choose the Command Field and edit BoundField properties. Change the SelectText property to 'Download'.
  7. Your web form should now be the same as in item 3.

    Now we need to write the script code to perform the upload and download.

    First the upload.

    Select the Upload File button and double click it. You will get an empty event handler for the button.
  8. The pseudo code for the event handler is as follows:
    If the upload control has a file attached
        Get the filename, content type, file size 
            and file data from it
        Open a connection to the database
        Build an SQL command for the insert
        Copy the data into the SQL command
        Execute the SQL command
        Update the GridView control
    
  9. Note: we already have a connection string and an insert SQL command in the SQLDataSource we used for the GridView. This means we can copy these when we need to use them. First put this library reference at the top of your .cs file.
          using System.Data.SqlClient
  10. Below is the actual code
    void Button1_Click(object sender, EventArgs e)
    {
      // If the upload control has a file attached
      if (FileUpload1.HasFile)
      {
        // Get the filename, content type, file size
        // and file data from it
        String Filename = FileUpload1.FileName;
        String ContentType = FileUpload1.PostedFile.ContentType;
        int FileSize = FileUpload1.PostedFile.ContentLength;
        Byte[] FileData;
        FileData = new Byte[FileSize];
        FileUpload1.FileContent.Read(FileData, 0, FileSize);
        // Open a connection to the database
        SqlConnection Conn = 
            new SqlConnection(SqlDataSource1.ConnectionString);
        Conn.Open();
        // Build an SQL command for the insert
        SqlCommand cmd = 
            new SqlCommand(SqlDataSource1.InsertCommand, Conn);
        // Copy the data into the SQL command
        cmd.Parameters.Add
           ("@Filename", SqlDbType.Text, 50).Value = Filename;
        cmd.Parameters.Add
           ("@ContentType", SqlDbType.Text, 50).Value = ContentType;
        cmd.Parameters.Add
           ("@DataSize", SqlDbType.Int).Value = FileSize;
        cmd.Parameters.Add
           ("@Filedata", SqlDbType.Image, FileSize).Value = FileData;
        // Execute the SQL command
        cmd.ExecuteNonQuery();
        Conn.Close();
        // Update the GridView control
        SqlDataSource1.Select(new DataSourceSelectArguments());
        GridView1.DataBind();
      }
    }
    

    Now we need to write the script code for downloading a file once it is selected in the GridView. When the user selects a row in a GridView, the SelectedIndexChanged event is fired. So we need a handler for this event. He we go!

  11. Select the GridView control on your form and choose the Event icon in the properties. Double click the SelectedIndexChanged event and you will get an empty event handler.
  12. The pseudo code for downloading a file is:
    Get the filename from the selected row of the GridView
    Open a connection to the database
    Build an SQL select statement to retrieve the record
    Copy the filename into the command    
    Execute the SQL statement
    Get the file size and file data from the retrieved record
    Send the retrieved file to the client
    
  13. The code for this is given below:
    void GridView1_SelectedIndexChanged(object sender, EventArgs e)
    {
      // Get the filename from the selected row of the GridView
      GridViewRow row = GridView1.SelectedRow;
      string Filename = row.Cells[1].Text;
      // Open a connection to the database
      SqlConnection Conn = 
        new SqlConnection(SqlDataSource1.ConnectionString); Conn.Open(); // Build an SQL select statement to retrieve the record SqlCommand cmd = new SqlCommand  ("Select * from [tblFileData] where [Filename] = @Filename;", Conn); // Copy the filename into the command cmd.Parameters.Add ("Filename", SqlDbType.NChar, 50).Value = Filename; // Execute the SQL statement SqlDataReader dr = cmd.ExecuteReader(); dr.Read(); // Get the file size and file data from the retrieved record int FileSize = dr.GetInt32(2); Byte[] FileData = new Byte[FileSize]; dr.GetBytes(3, 0, FileData, 0, FileSize); Conn.Close(); // Send the retrieved file to the client Response.Clear(); Response.ContentType = "application/octet-stream"; Response.AddHeader ("Content-Disposition", "attachment; filename=\"" + Filename + "\""); Response.Flush(); Response.BinaryWrite(FileData); }

Download a ZIP version of UploadDB.

Uploading and downloading files saved externally to the Database

A different scenario will be used for this example so that we can show more features of the .NET environment. We will create a photo library which will only accept the upload of JPEG files with extension .jpg.

Before looking at the process in detail, let us look at the solution from the point of view of the design and development process. The main difference between this approach and the previous one is the need to generate unique filenames when files are uploaded. The problem is that when we insert a new record, we need to know the autogenerated key for the new record so that we can use it for the filename. To make this work we have to use a special combination of SQL query statements. The first is the insert query and the second is a select statement which returns the value of the autogenerated key. As long as the two statements are executed together the second part returns the actual key value. For this example we will only use a simple table with a key field and a filename. The SQL for this is:

INSERT INTO tblUploads (Filename) VALUES (@Filename);
SELECT @FileID = SCOPE_IDENTITY();
    

The INSERT statement uses the value of the Filename from the Upload control.
The SELECT statement receives the value of the key field into the Parameter @FileId.

This value can be used to make the filename for storing the image in the upload folder.

Download could be done as with the previous example, but we can make the interface more effective by having an image control on the web form and making it display the current selected image.

Step by step

  1. Create a new C# ASP.NET Website called UploadSeparate.
  2. Create a new database, called Upload.mdf with a table called tblUploads, matching the following structure:
    JPEG Upload - database structure
    Make sure Filename is large enough to hold meaningful data (minimum of 50 characters) and make the FileId an Identity field, like the previous example.
  3. Build a user interface matching the following display, noting that you may initially have four columns displayed in your GridView, and 'View' links may read 'Select':
    JPEG Download - user interface
     
  4. Select the Upload File button and double click it. You will get an empty event handler for the button.
    First put these library references at the top of your .aspx.cs file.
    using System.Data.SqlClient;
    using System.IO;
  5. Below is the actual code for the button click:
    protected void Button1_Click(object sender, EventArgs e)
    {
     Label1.Visible = true;
     if (FileUpload1.HasFile)
      if (System.IO.Path.GetExtension
              (FileUpload1.FileName).ToLower() == ".jpg")
      {
       SqlConnection conn =
         new SqlConnection(SqlDataSource1.ConnectionString);
       SqlCommand cmd =
         new SqlCommand
           (@"INSERT INTO tblUploads (Filename)
                     VALUES (@Filename);
              SELECT @FileID = SCOPE_IDENTITY();",
            conn);
       cmd.Parameters.Add
          ("@Filename",
           SqlDbType.NVarChar, 50).Value = FileUpload1.FileName;
       cmd.Parameters.Add("@FileID", SqlDbType.Int, 4);
       cmd.Parameters["@FileID"].Direction =
                                    ParameterDirection.Output;
       conn.Open();
       cmd.ExecuteNonQuery();
       FileUpload1.SaveAs
           (Server.MapPath("~/Uploads/") + 
            cmd.Parameters["@FileID"].Value.ToString() + ".jpg");
       GridView1.DataBind();
       Label1.Visible = false;
      }
    }
  6. The code for Gridview.SelectedIndexChanged is quite straightforward. It simply retrieves the selected row, makes a URL from the key and sets the ImageUrl for the Image control. The old filename is used to set the AlternateText. The code looks like this:
    protected void GridView1_SelectedIndexChanged
                     (object sender, EventArgs e)
    {
      GridViewRow row = GridView1.SelectedRow;
      Image1.ImageUrl = "~/uploads/" + row.Cells[1].Text + ".jpg";
      Image1.AlternateText =
                     "Original filename: " + row.Cells[2].Text;
    }

Download a ZIP version of the UploadSeparate application.

Valid XHTML 1.0! | Valid CSS! | WCAG Approved AA
Page design by: John P Scott - Hosting with: Netcetera