Upload files to the server using Javascript and MVC WebAPI

The ASP.NET WebAPI is really versatile and powerful and I like to use it as much as I can when I develop for the web. I know that MVC controller methods can also process ajax requests, but I like the separation of concerns. WebAPI for REST calls and MVC for Views and the ViewModels manipulation.

Note: if you still want to use MVC instead of WebAPI for your server implementation, have a read here.

Recently, I had to implement a method to upload files to the server from an MVC view. I decided to use ajax and WebAPI. Once you get your head around it, it’s fairly simple. We will start with the WebAPI code

WebAPI Controller Method

[HttpPost]
public async Task<HttpResponseMessage> UploadFile(string id)
{
    if (!Request.Content.IsMimeMultipartContent())
    {
        return Request.CreateErrorResponse(HttpStatusCode.UnsupportedMediaType, "The request doesn't contain valid content!");
    }

    try
    {
        var provider = new MultipartMemoryStreamProvider();
        await Request.Content.ReadAsMultipartAsync(provider);
        foreach (var file in provider.Contents)
        {
            var dataStream = await file.ReadAsStreamAsync();
            // use the data stream to persist the data to the server (file system etc)

        var response = Request.CreateResponse(HttpStatusCode.OK);
        response.Content = new StringContent("Successful upload", Encoding.UTF8, "text/plain");
                   response.Content.Headers.ContentType = new MediaTypeWithQualityHeaderValue(@"text/html");
         return response;
    }
    catch (Exception e)
    {
        return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e.Message);
    }
}

The above code is fairly simple. First we check if the data posted to this method is valid (i.e a file)
if (!Request.Content.IsMimeMultipartContent()).
Then we create a StreamProvider in order to read the request contents asynchronously like this:
var provider = new MultipartMemoryStreamProvider(); await Request.Content.ReadAsMultipartAsync(provider);

BONUS POINTS: along with any files, you can post additional data, such as strings or json objects etc. In this example, the API method accepts an ID string parameter.

Since we have the ability to post multiple files, we need to retrieve them one at a time:

foreach (var file in provider.Contents)
{
  var dataStream = await file.ReadAsStreamAsync();
  // rest of code omitted for clarity
}

Finally we create and send a response to the calling JavaScript method

HTML/Javascript code

First we need to add an <input type=file... element on the page to allow users to browse and upload files.

<div>
    <input type="file" name="UploadFile" id="txtUploadFile" class="homereportupload"  accept="application/pdf" />
</div>

In this particular example I also restrict the accepted files to pdf only. For details on the available list of accepted values you can have a look here.

Now, lets add the javascript code to glue everything together:

$('#txtUploadFile').on('change', function (e) {
    var files = e.target.files;
    if (files.length > 0) {
       if (this.value.lastIndexOf('.pdf') === -1){
          alert('Only pdf files are allowed!');
          this.value = '';
          return;
       }
       if (window.FormData !== undefined) {
           var data = new FormData();
           for (var x = 0; x < files.length; x++){
               data.append("file" + x, files[x]);
           }

           $.ajax({
               type: "POST",
               url: '/api/MyController/UploadFile?id=' + myID,
               contentType: false,
               processData: false,
               data: data,
               success: function(result) {
                   console.log(result);
               },
               error: function (xhr, status, p3, p4){
                   var err = "Error " + " " + status + " " + p3 + " " + p4;
                   if (xhr.responseText && xhr.responseText[0] == "{")
                       err = JSON.parse(xhr.responseText).Message;
                       console.log(err);
                    }
                });
        } else {
            alert("This browser doesn't support HTML5 file uploads!");
          }
     }
});

This method works by attaching an event listener to the input element using jQuery and 'fire' on change, i.e when the user browses and selects a file(s). We then check to ensure that the selected file(s) is a .pdf and proceed to create a new FormData object where we load all the files.

The last step is to perform the ajax call which posts the data to the WebAPI contoller and logs the success to the console.

I hope this helps you in some way and, as always, use the comments to let me know if you have any issues or questions

P.S Make sure you follow me on Twitter @christosmatskas for more up-to-date news, articles and tips.