HTML5 drag and drop asynchronous multi file upload with ASP.NET WebAPI

Strath

HTML5 drag and drop asynchronous multi file upload with ASP.NET WebAPI

Because HTML5 with ASP.NET Web API is a lot of fun

Today we are going to build a neat HTML5 file uploader using ASP.NET Web API and jQuery. We are also going to include knockout.js to keep the list of uploaded files updating smoothly in real time.

In addition to all that, we will leverage on HTML5 drag and drop events , as well as HTML5 File API, to provide the file input to the application. Finally, we will use FormData JS interface to build up the request, and we will use ApiController of our ASP.NET MVC 4 application to pick up the files and save them on the server using an instance of MultipartFormDataStreamProvider.

More after the jump.

Important update

This post was originally written against Web API Beta and then updated to RC. However, now with the RTM version of Web API out, please make sure to read the new post about handling the uploads in ASP.NET Web API. No changes to the HTML5 bits required.

Source code is here on GitHub.

So, again, what are we going to do?

Our sample app will be rather simple. A simple web page, with a drop area to which users can drag files. Once you drag and drop a file, an AJAX request will be triggered sending the file over to the server which will save it in an appropriate folder, and send back a JSON file containing basic file information.

Building up the basics

Well, we start off with an ASP.NET MVC 4 application, Web API project.

We leave everything intact for now, and go straight to our “Master Page” – the out of the box _Layout.cshtml. We need to include jQuery, knockout.js and a reference to script.js, which will be our application’s JavaScript container file.

Handling JavaScript events

Next step is to focus on the JS scripting. We will need to handle 3 events of the HTML5 drag and drop interface – dragEnter, dragExit and drop.

We can do that on document ready using jQuery.

Div with the ID “#ulbox” is going to be our drop area (upload zone). We bound, as mentioned, 3 event handler. Let’s inspect them.

Enter and Leave events are very simple, all we do is stop the event propagation (not really necessary in our simple examples, but good practice) and add/remove a class to the drop area indicating that someone is dragging something in or out of the area. This allows us to indicate to the user that this is an active area.

Drop event is a bit more interesting.

First, we do the same as we would on the exit event – remove the “over” indication. Next, we grab the files using File API, from the dataTransfer property. Notice we reference evt.originalEvent.dataTransfer, rather than evt.dataTransfer. Since we bound the events using jQuery, evt object is a javascript event, whereas evt.originalEvent is a native JavaScript event which we are interested in. By default (without extending $.event manually) evt.dataTransfer would be undefined.

If we have any files, we process the upload. Let’s have a closer look then:

Since we are going to use FormData to build up the request, we first check if the browser supports it. Then we loop through the files and append them one by one to FormData object, using items[] key. This way any subsequent append, doesn’t overwrite the previous one.

Next step is to do an AJAX post, and we will use a properly set up $.ajax call for that. Request type is obviously POST, and we are going to post to /api/uploading (which immediately suggests that our ApiController will be called UploadingController).

We don’t need to provide a content type (FormData object will default to multipart/form-data) and we set processData to false, as we don’t want jQuery to convert our data to application/x-www-form-urlencoded, as it would automatically attempt to do otherwise.

Finally, we set data to the FormData instance we built earlier, and in the callback we iterate through the JSON response and push the object to knockout.js observablearray of the main viewModel (don’t worry, we are getting there).

Displaying uploaded files list

Now that we have all the HTML5 goodies in place, let’s set up the aforementioned viewModel.

The viewModel will only have one property, uploads, where it will keep track of the uploaded files.

The HTML is going to be pretty simple. Since we set up the _Layout.cshtml already, we will now just need to worry about the body of the default view (home/index).

I’m sure you recognize the familiar knockout.js bindings. First we have the drop area, then an unordered list which is bound to the viewModel’s uploads property. The template responsible for each item renders all the details regarding the recently uploaded file. The object properties are determined by the JSON object retruned from our ApiController (we’ll get to that soon).

CSS to add some look and feel

With all that, we just need a bit of CSS to spice things up, and our UI is 100% ready and waiting for the server’s ApiController to start accepting them uploads.

Going server side

Server side of our application is not going to be very complicated. After all ASP.NET MVC and ASP.NET Web API are there to make things easy and smooth as usually.

Essentially, all we need one ApiController, as mentioned earlier. In addition, since we wanted to return some basic information about the uploaded files, we’ll create a simple custom class as well.

The custom class will provide information about filename, filesize and server file path after the upload & save process on the server is completed.

The ApiController will contain one async method – Post(). It will return an IList of FileDesc objects. As any C# 5.0 async methods, it’s wrapped in a Task T wrapper.

The code is quite simple but let’s walk through it quickly. First we check if the content is indeed MIME/multi-part. Then we instantiate MultipartFormDataStreamProvider using the PATH to the location where we want to save the files in. Next step is to read the multipart body of the request in an async wait (hence, await method). This also saves the files physically on the HDD. We need to change the name later on using File.Move, since MultipartFormDataStreamProvider saves with value and we need the key. The Multipart handling is going to change after the RC (in fact if you grab latest source from Nuget nightly builds you’d see it already did, I will blog about it in the future), but with Web API RC, it is how it is, so this unpleasant step has to be taken.

Then we create an IDictionary which will contain key/value pairs of the file names and contents of the files that have just been saved. Finally we use that IDictionary in conjunction with FileInfo object to build up a List of FileDesc (our custom) objects to return.

Trying the upload yourself

That’s it, amazingly simple, isn’t it? Now let’s try it out. The only thing to mention here is that, since we are heavily relying on HTML5 (File API, drag and drop) and XHR2 (FormData) the functionality will only work in the modern browsers – according to this article, it is supported by:
- Chrome 7+
- Firefox 4+
- IE 10+
- Safari 5+

Surprisingly, no Opera support.

Ok, so launch the website and try to drag some files over. Our humble interface is shown on the screenshot below.

Notice that, since we set up the dragenter and dragout events, the drop area changes color when it needs to. Also, Windows 8, in a very nice way tells us that we are about to “move” some files.

When we drop the files, the upload happens automatically, and knockout.js powered upload list informs us about the successfully uploaded files immediately.

We can also navigate to the physical folder itself to verify that the upload happened without any problems indeed.

Source files & Summary

Again, as it is always the case with tutorial examples, there is plenty more that can be done – i.e. adding progress bars, or some sophisticated graphics. You can do it yourself, because… as usually, you can grab the source files! :)

Remember, this is a VS12RC project so if you don’t have it yet, grab the beta asap!
– source code is on GitHub

Be Sociable, Share!

  • http://leniel.net Leniel Macaferi

    Hey Filip,

    Amazing post! :D Enjoyed and learned a little bit more as always.

    It’s great to see how things fit. I’ve read about Knockout.js last month and now that helped me understand this post.

    Keep posting great stuff as this.

    All the best,

    Leniel

  • http://www.programandonet.com Pablo Bouzada

    Great post Filip. Thanks a lot for sharing your knowledge.

  • http://rminchev.wordpress.com Rado

    very cool post!

  • Pingback: Dew Drop – April 5, 2012 (#1,300) | Alvin Ashcraft's Morning Dew

  • Renato

    Great post! It is possible to zip the files automatically before upload?

  • Steve

    This doesn’t work for me on Chrome version 18

  • Steve

    Nevermind, must have had a typo as the sample download works.

    Thanks!

  • James

    Hmmm – I got this to work in Chrome, but IE9 grabs the file first to download it…

    • James

      Actually – the latest version of FireFox does the same thing…

    • James

      Oops – I meant IE10… ;)

    • Dominik

      Unfortunately I’ve got the same problem! It would be great if someone has an idea how to fix it. It works fine in Chrome – but not in IE and Firefox

      • http://www.luxuryspa.se Tomas

        It seems that when you drop a file in other browsers than Chrome the browser opens that file instead of triggering the upload command :( .

    • James

      I was able to get prevent the default behaviors of dropping a file onto the page in both FireFox and IE10 (I still however have not gotten the whole script to work in IE10 – FireFox is fine.)

      It appears using the native JavaScript event and preventDefault() suppresses the usual behavior. The jQuery object will not work.

      I changed how the ul was grabbed and events assigned as follows:

      var box = document.getElementById(‘ulbox’);
      box.ondragover = dragEnter;
      box.ondragend = dragLeave;
      box.ondrop = drop;

      Then in the drop function I change

      var files = evt.originalEvent.dataTransfer.files;

      to

      var files = evt.dataTransfer.files;

      Note: I did attempt in the drop function just to change evt.preventDefault() to evt.originalEvent.preventDefault(), but that was unsuccessful.

      Now on to figure out why IE10 still isn’t completing the call to the service…

    • James

      One more update…

      Looks like this is working fine in IE10 if you make the changes I described above AND do not use a file that is 0 bytes. Odd – FireFox and Chrome handle this fine. IE10 will upload any file that is > 0 bytes.

      • Filip W

        James, thanks a lot for your input, I will update the post accordingly!

  • Pingback: April 14th Links: ASP.NET, ASP.NET MVC, ASP.NET Web API and Visual Studio | nalli.net

  • Pingback: » April 14th Links: ASP.NET, ASP.NET MVC, ASP.NET Web API and Visual Studio HD Software Co. Blog

  • Jon

    Hi

    Cool post but one question. How would you make this work without the async and await? I’m trying this out in a normal asp.net site with the API controllers that have just been released. Not using an MVC site and using VS2010. Am I on to a loser? I tried installing the async CTP 3.0 but it fails with a fatal error all the time. So yeah basically can this be done without the async?

    Thanks
    Jon

  • http://www.csharpaspnetarticles.com http://www.csharpaspnetarticles.com

    Very informative, thanks

  • http://www.dotnetjalps.com Jalpesh Vadgama

    Awesome post!! Keep doing good work!!

  • Ilya

    Amazing!!! Thank you very much for this post.

  • tomek

    I just like the helpful info you provide to your articles. I will bookmark your blog and check once more right here regularly. I’m reasonably sure I’ll learn lots of new stuff right here! Good luck for the next!

  • http://blog.agilehobo.com Agile Hobo

    I use your code with a simple HTML form, it only works on IE but doesn’t work on Firefox and Chrome.

  • http://blog.agilehobo.com Agile Hobo

    I found the reason.

    Actually I made some changes on your code. It makes the new code works on IE, but not on Firefox and Chrome.

    The reason is the key of uploaded items from IE in stream.BodyPartFileNames is full path, while it’s file name only on Firefox (maybe same with Chrome, I didn’t check it yet)

  • Pingback: HTML5 drag and drop asynchronous multi file upload with ASP.NET WebAPI | StrathWeb | Web tools and technologies | Scoop.it

  • http://www.subramanyamv.blogspot.com Subramanyam Vijayanagaram

    Excellent article.

  • Jon

    Anyone know how you would upload to a virtual directory using this API? At the moment the path is hardcoded which is fine for tutorials. I did think about using Server.MapPath but that doesn’t seem to work.

    Thanks

  • Jon

    Also, is it possible to send additional data with the request? Maybe some sort of ID’s?

  • George

    Great job

  • Shmoo

    Dont work with me.
    I use VS2012RC on Win2008R2, just use the sample code.
    If i drop something in the drag n drop field the browser (firefox and IE) just try to open the file in the browser or ask me if i want to save or open the file.
    any ideas why this happen?

  • Jordan

    It works Perfect for me. Great post. Does anyone know how to put progress bar ?

  • Pingback: Returning Void in Async method from WEB API Controller

  • Pingback: Exemple de file upload « Pragonas

  • http://scrapstamper.blogspot.com Kerry Lantelme

    You need to participate in a contest for one of the best blogs on the web. I will recommend this site!

  • http://twitter.com/korzeniow Łukasz Korzeniowski

    You have error in your code, there is no ‘dragexit’ event. In this fragment of code:

    $(document).ready(function () {
    var $box = $(“#ulbox”);
    $box.bind(“dragenter”, dragEnter);
    $box.bind(“dragexit”, dragLeave);
    $box.bind(“drop”, drop);
    });

    It should be ‘dragleave’. It took me a while to figure out why it isn’t working.

    • http://www.strathweb.com/ Filip W

      That’s because it’s an old post – when it was written, “dragexit” was part of the experimental File API spec, but is now deprecated (i.e. latest Chrome doesnt support it anymore). However, if you followed the link to the latest code on GitHub (I linked to it under “important update”), that already had it changed to “dragleave”.

  • Pingback: benpowell.org

  • Daniel Quagliano

    This is the second time I use some of your code, great blog dude.

    Just one comment, I had to block regular behavior on dragover, otherwise the browser always opened the file.

    $(‘#ulbox’).on(“dragover”, function (e) {
    e.preventDefault();
    });

  • jens07

    IEnumerable bodyparts =
    await Request.Content.ReadAsMultipartAsync(stream);

    This line keeps giving typecast errors. Any solutions?

  • jens07

    IEnumerable bodyparts =
    await Request.Content.ReadAsMultipartAsync(stream);

    This line keeps giving typecast errors. Any solutions?