Uploading Files Asynchronously using ASP.NET Web API

Introduction

Uploading files from the client machine to the web server is a very common operation. Typically ASP.NET developers use HTML file input fields and form submission to accomplish this task. By default this file upload process is synchronous. By using Web API in combination with the asynchronous programming features of the .NET framework you can easily upload files on the server in asynchronous manner. In this article you will learn how to initiate the file upload operation using jQuery Ajax and then save the posted files on  the server using asynchronous Web API.

Sample Form to Upload Files

To illustrate how files can be uploaded to the server in asynchronous fashion you will develop a simple page as shown below:

Upload to the Server
Upload to the Server

The ASP.NET MVC view shown above contains an HTML5 file input field and a button. HTML5 allows you to select multiple files to upload to the server. All you need to do is set the multiple attribute of the file input field. The following markup shows how this can be done:

<form>
    <span>Select file(s) to upload :</span>
    <input id="file1" name="file1" type="file" multiple="multiple" />
    <input id="button1" type="button" value="Upload" />
</form>

The above markup shows a <form> tag that houses a file input field and a button. Notice that the <form> element doesn’t have its action and method attributes set. That’s because you will be using jQuery to upload the files. The file field has its multiple attribute set so that multiple files can be selected. The button is not a submit button, it’s a plain button since we won’t submit the files through a traditional form submission.

Creating a Web API for Receiving and Saving the Uploaded Files

To develop this example, begin by creating a new ASP.NET web application based on the Web API project template (see below).

Web API Project Template
Web API Project Template

Change the default ApiController name (file name as well as the class name) to FileUploadController. Delete all the methods from the ApiController except Post(). This is done since file upload will be done using an HTTP POST request. Then modify the Post() method as shown below:

public async Task<List<string>> PostAsync()
{
    if (Request.Content.IsMimeMultipartContent())
    {
        string uploadPath = HttpContext.Current.Server.MapPath("~/uploads");

        MyStreamProvider streamProvider = new MyStreamProvider(uploadPath);

        await Request.Content.ReadAsMultipartAsync(streamProvider);

        List<string> messages = new List<string>();
        foreach(var file in streamProvider.FileData)
        {
            FileInfo fi = new FileInfo(file.LocalFileName);
            messages.Add("File uploaded as " + fi.FullName + " (" + fi.Length + " bytes)");
        }

        return messages;
    }
    else
    {
        HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.BadRequest, "Invalid Request!");
        throw new HttpResponseException(response);
    }
}

As you can see the Post() method is now changed to PostAsync() to reflect its asynchronous nature. It is also marked with async keyword and returns a List of strings wrapped in a Task object. This generic List of strings holds success messages indicating the file path on the server and its size in bytes. You will understand how these messages are generated later in the code.

The PostAsync() method first checks whether the form content being posted is multipart using the IsMimeMultipartContent() method. The IsMimeMultipartContent() method returns true if the content is multipart content. It then determines the destination folder for saving the uploaded files. In this example a fixed folder name – uploads – is used but you can make it more configurable by picking it from the <appSettings> section so that you can easily change it later if required. The uploadPath variable stores the full server side path of the destination folder as returned by the MapPath() method of the Server object.

Then an instance of MyStreamProvider class is created. The MyStreamProvider class is a custom class that inherits from the MultipartFormDataStreamProvider base class. The MultipartFormDataStreamProvider class resides in the System.Net.Http namespace and provides the basic functionality of saving the uploaded files. The MultipartFormDataStreamProvider checks the Content-Disposition header and determines an output stream based on the presence of the filename parameter. If a filename parameter is present then the content is written to a file stream otherwise it is written to a memory stream. Just to understand how a content disposition header looks, see the following sample header:

content-disposition: form-data ; name="file1"; filename="employees.xml"
Content-Type: text/plain

Why do you need to create a custom class based on MultipartFormDataStreamProvider? Because the default implementation saves the files with arbitrary file names on the server. If you wish to have the destination file name the same as the source file name from the client machine you need to do that job on your own. That is what MyStreamProvider class does. Here is how:

public class MyStreamProvider : MultipartFormDataStreamProvider
{
    public MyStreamProvider(string uploadPath)
        : base(uploadPath)
    {

    }

    public override string GetLocalFileName(HttpContentHeaders headers)
    {
      string fileName = headers.ContentDisposition.FileName;
      if(string.IsNullOrWhiteSpace(fileName))
      {
        fileName = Guid.NewGuid().ToString() + ".data";
      }
      return fileName.Replace("\"", string.Empty);
    }
}

As you can see the MyStreamProvider class inherits from the MultipartFormDataStreamProvider base class and overrides the GetLocalFileName() method. Inside the GetLocalFileName() method you essentially return a file name by picking it from the ContentDisposition header. While instantiating the MyStreamProvider object the full destination folder path where the file needs to be saved is passed in the constructor (see the PostAsync() method discussed earlier).

The PostAsync() method then calls the ReadAsMultipartAsync() method. The ReadAsMultipartAsync() method reads MIME body parts in asynchronous fashion and depending on their content disposition they are saved as files as explained earlier. Notice that the call to ReadAsMultipartAsync() uses await keyword.

It would be helpful if after uploading the files on the server you send the file details to the client so that the same can be displayed to the end user. This can be done by returning a string with the full server side path of the file and its size in bytes. The FileData property of the MultipartFormDataStreamProvider class provides a collection of multipart files that are being uploaded. So, the foreach loop iterates through all the files in this collection and for each file a string message is created. To know the full name and size of a file, FileInfo class is used.

The else part of the if condition (that is, the request doesn’t contain multipart content) throws an exception since the content is not multipart content. This is done using the HttpResponseException class and passing it a HttpResponseMessage object with HttpStatusCode of BadRequest.

This completes the Web API. Now let’s develop a jQuery client that calls this Web API.

Uploading Files using jQuery

To consume the Web API you have just developed, add an Index view (Index.cshtml) for the HomeController’s Index() action method. Make sure to add the <form> element to the Index view as discussed earlier in this article.

In order to upload files using jQuery you need to programmatically construct a FormData object on the client side and then pass it to the server. The FormData object is passed to the server using jQuery $.ajax(). The following jQuery code shows how this is done.

$(document).ready(function () {
    $("#button1").click(function (evt) {
        var files = $("#file1").get(0).files;
        if (files.length > 0) {
            var data = new FormData();
            for (i = 0; i < files.length; i++) {
                data.append("file" + i, files[i]);
            }
            $.ajax({
                type: "POST",
                url: "/api/fileupload",
                contentType: false,
                processData: false,
                data: data,
                success: function (messages) {
                    for (i = 0; i < messages.length; i++) {
                        alert(messages[i]);
                    }
                },
                error: function () {
                    alert("Error while invoking the Web API");
                }
            });
        }
    });
});

The ready() function wires the click event of the Upload button with an event handler function. The click event handler retrieves the collection of files selected by the user for uploading. Recollect that the file input field has its multiple attribute set. The files selected by the use are available via files collection of the file input field. Notice that to get ahold of this collection you need to use get(0) of jQuery that returns the raw DOM element (file field in this case).

Then a FormData object is programmatically constructed and populated with selected files. To send the files to the server you use $.ajax() of jQuery. The type of the Ajax request is POST (this verb must match the Web API method we handled earlier). The Url option specifies the name of the Web API end point (/api/fileupload in this case). The data option is set to the FormData object you created earlier. The contentType and processData options are set to false because you wish to upload a multipart content in raw fashion rather than its URL encoded version (the default way). The success function receives an array of messages you return from the PostAsync() method. The for loop iterates through this array and displays the string messages returned from the Web API to the end user. A sample alert would look like this:

Sample Alert
Sample Alert

The error function simply displays an error message to the user.

That’s it! You can now run the Index view and test how the files are uploaded to the server.

Summary

Uploading files from the client to the web server is a common requirement. Using ASP.NET Web API combined with the async / await programming model you can easily develop a solution that uploads files asynchronously. In this article you learned how the upload operation can be performed using jQuery and FormData object. You also learned to save the uploaded files on the server by creating a custom implementation of MultipartFormDataStreamProvider class.

Extra Reading Links

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read