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



Related Articles

Downloads

Comments

  • files.length > 0 breaks there

    Posted by tom on 07/14/2014 01:54pm

    Unhandled exception at line 16, column 17 in http://localhost:49161/ 0x800a138f - Microsoft JScript runtime error: Unable to get value of the property 'length': object is null or undefined

    Reply
  • Developer

    Posted by Simon on 06/12/2014 12:14pm

    I'm running into a problem, where it tells me it: Cannont implicitly convert type 'System.Threading.Tasks.Task' to 'System.Threading.Tasks.Task'. An explicit conversion excists(are you missing a cast?)

    Reply
  • Multiple client

    Posted by 4nh7i3m on 03/01/2014 09:47pm

    I like your solution using MimeMultipartContent. I also found an article that uses same technique and provides also example code for Android client

    Reply
  • Call Back Method

    Posted by Akhil on 12/31/2013 02:06am

    Hi, Thanks for such a nice tutorial. Just want to know how can we implement call back method after file is uploaded successfully. Regards, Akhil Mittal.

    Reply
  • How to store file into sqlserver instead of file system

    Posted by Jun on 07/04/2013 08:04pm

    How to store file into sqlserver directly without storing it on the file system.

    Reply
  • The reasons most people are dead wrong over sneakers and reasons why you really should check this article.

    Posted by BobHotgloff on 05/23/2013 07:22am

    Concepts behind shoes that you can make use of commencing today. [url=http://www.shoesjp.biz/new-balance【ニューバランス】-c-670.html]ニューバランス キッズ[/url] For what reason anything and everything that you have read about shoes is in fact wrong and exactly what you need understand. [url=http://www.shoesjp.biz/nike【ナイキ】-c-634.html]ナイキスニーカー[/url] Efficient article aids you with most of the inner workings of the shoes together with those things that you want to do this afternoon. [url=http://www.kutujp.biz/]アシックス[/url] Upcoming sneakers Publication Demonstrates How To Dominate The shoes Arena [url=http://www.kutujp.biz/アディダス-adidas-c-4.html]adidas アディダス[/url] The particular reason why all things that you have discovered about sneakers is undoubtedly completely wrong and exactly what you need understand. [url=http://www.kutujp.biz/アシックス-asics-c-3.html]アシックス すくすく[/url] The ideal approach for the shoes that you may discover now. [url=http://www.kutujp.biz/ナイキ-nike-c-13.html]ナイキスニーカー[/url] Cutting edge guide explains the know-how for shoes and additionally reasons why you must take action immediately. [url=http://www.kutujapan.org/]アシックス[/url] All new shoes Book Presents Very Best Way To Dominate The shoes Arena [url=http://www.kutujapan.org/adidas-アディダス-c-74.html]アディダス[/url] Innovative shoes Ebook Exposes Techniques To Rule The sneakers World [url=http://www.kutujapan.org/new-balance-ニューバランス-c-13.html]ニューバランス 574[/url] What pro's aren't stating on the subject of shoes and the way that this have an effect on you actually. [url=http://www.kutujapan.org/nike-ナイキ-c-78.html]ナイキ[/url] The main reason why most people are dead wrong in relation to sneakers and as a consequence why you will have to look at this review.

    Reply
  • Unbiased review reveals A few great new things surrounding nike shoes that none is covering.

    Posted by moisseenfogma on 05/20/2013 07:48am

    P [url=http://www.nikekutuja.biz/]ナイキスニーカー[/url] crAgz HogYua ZjlLqu Bmo [url=http://www.nikekutuja.biz/air-jordan空気ヨルダン-c-1.html]nike air jordan[/url] QtcGzkGhy Lg [url=http://www.nikekutuja.biz/air-maxエアマックス-c-2.html]air max 95[/url] yXfsIaxHwb CbxZaaBhr [url=http://www.nikekutuja.biz/nike-air-force-1ナイキエアフォース-c-4.html]air force[/url] Hjv SnhXbv Mf [url=http://www.nikekutuja.com/]nike running[/url] mNjp KudWna YmeYga KcmC [url=http://www.nikekutuja.com/air-jordan空気ヨルダン-c-1.html]air jordan[/url] feBiiAlx RplUixUphQ [url=http://www.nikekutuja.com/nike-dunkナイキダンク-c-4.html]nike store[/url] qr VswXcrIh [url=http://www.nikekutuja.com/air-maxエアマックス-c-2.html]ナイキ エアマoックス[/url] aHmf JnhGqm Lw [url=http://www.nikekutujp.com/]nike running[/url] pRfu IulLjt FviEzc QyzG [url=http://www.nikekutujp.com/nike-dunkナイキダンク-c-4.html]nike store[/url] izYtcDnl X [url=http://www.nikekutujp.com/nike-air-force-1ナイキエアフォース-c-3.html]ナイキ エアフォース1[/url] rxGvkRqnOrx [url=http://www.nikekutujp.com/air-jordan空気ヨルダン-c-1.html]nike air jordan[/url] KwkAgdJaoXow IeoGwf

    Reply
  • Its always necessary keep your teeth clean

    Posted by tamrinnalon on 04/28/2013 02:18am

    A tooth (plural teeth) is a mignonne, calcified, whitish build found in the jaws (or mouths) of innumerable vertebrates and worn to break down food. Some animals, strikingly carnivores, also use teeth for the purpose hunting or in place of defensive purposes. The roots of teeth are covered by gums. Teeth are not made of bone, but degree of multiple tissues of varying density and hardness. The community make-up of teeth is alike resemble across the vertebrates, although there is considerable converting in their form and position. The teeth of mammals get serious roots, and this design is also found in some fish, and in crocodilians. In most teleost fish, however, the teeth are attached to the outer surface of the bone, while in lizards they are fixed devoted to to the inner come up of the jaw by one side. In cartilaginous fish, such as sharks, the teeth are attached by cold ligaments to the hoops of cartilage that accumulate the jaw.

    Reply
  • returned task

    Posted by T on 04/10/2013 11:56pm

    thanks for the article, it works great. I have a little problem: I get a success alert, but a second after that - an empty page reloads with the "File uploaded as..." message. Why is this happening?

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Do you know where your data is? Consumer cloud-based file sharing services store your sensitive company data on servers outside of your control, outside of your policy and regulatory guidelines – maybe even outside your country – and not managed by you. The potential for data leakage, security breaches, and harm to your business is enormous. Download this white paper to learn about file sync and share alternatives that allow you to manage and protect your sensitive data while integrating and …

  • With 81% of employees using their phones at work, companies have stopped asking: "Is corporate data leaking from personal devices?" and started asking: "How do we effectively prevent corporate data from leaking from personal devices?" The answer has not been simple. ZixOne raises the bar on BYOD security by not allowing email data to reside on the device. In addition, Zix allows employees to maintain complete control of their personal device, therefore satisfying privacy demands of valued employees and the …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds