Async Ajax file upload sounded like “science fiction” to me up until some time ago. I remember those days, back in Classic ASP, when I had to employ 3rd party components to have the browser upload files. When ASP.NET introduced FileUpload control, I was truly excited. It was plain easy to use. However, it still required a full postback. Several months ago I saw that AjaxControlToolkit provided an AsyncFileUpload control, and I think that this was the first time I’ve heard of the idea to provide async file upload. However, a quick test I performed back then failed, and I decided to forgo the idea till bugs are fixed and things become more stable. I even developed a File Upload control in Silverlight which provided me with an async upload (as well as other features I needed back then). Basically I continued to use ASP.NET’s excellent FileUpload where possible, up until today, as I had a situation in which I preferred to check on async upload solutions again.
So, a quick search in Google led me to check out this jQuery File Uploader. It looked good, looked like it was working ok, and there was a sample code in C# (WebForms) which got me going quickly (MVC sample is also available). One last thing: although it seems to have jQuery UI support for the “look and feel”, I wanted the most basic functionality of uploading a file asynchronously as I had to reuse graphics from the existing project.
The sample code taught me that html now allows multiple file selection. This was new to me. I tried to track this down and found out the the “multiple” attribute is still in draft, but is already working in some of the newer browsers (unfortunately, IE9 does not seem to support it).
Core ajax upload
In order to get things going, you’ll need the following (stated also in the requirements page):
- jQuery 1.6 or above.
- jQuery UI 1.8 or above (widget factory is the minimum requirement).
- jQuery File Upload plugin. For the most basic implementation, you’ll only need the files jquery.fileupload.js and jquery.iframe-transport.js from that zip.
I based the following code on the “Basic Plugin” instructions. Here’s the client code:
- Lines 9-12 are the basic scripts required. I noticed that without the iframe-transport js file, IE doen’t upload anything. As for the jQuery and jQuery UI, you may choose to use the available CDN’s, such as Google Libraries API or Microsoft Ajax Content Delivery Network.
- Line 15 activates the fileupload plugin.
- Line 16 seems to be required in IE9 for repeated uploads. The documentation states that “By default, the file input field is replaced with a clone after each input field change event. This is required for iframe transport queues and allows change events to be fired for the same file selection, but can be disabled by setting this option to false.” On my machine IE9 failed to upload after the first attempt, unless I set this flag to false. UPDATE: On another website I’m developing, setting this flag to true or false made no difference and IE failed repeated uploads. I’m not sure why, but I just guess that there was some coincidental conflicts between this mechanism and the website’s that caused this to malfunction. What I ended up doing was replacing the file field with jQuery, each time the ‘done’ callback was called, and activated the plugin on the new file field.
- Line 17 states that the result is expected on json format.
- Line 18 sets the upload url.
- Lines 19-23 handle the result using the ‘done’ callback (as shown in the File Upload “basic plugin” page.
- Line 28 defines the html file upload with the multiple attribute (“multiple” is not required for running this sample).
The server side implementation is a standard ashx. I based it on Bo Schatzberg’s sample:
- Line 10 checks if there are files being uploaded. I wasn’t familiar with the context.Request.Files collection before, so thanks Bo! You may want to log and return a message if there are no files.
- Lines 12-14 establish a target location for the uploaded files.
- Line 16 gets the file. You may ask yourself why this implementation doesn’t iterate over the context.Request.Files collection to get all the files (after all, we did ask for multiple file selection and uploads). It did, originally, but then I noticed that although the files are multiply selected, they are uploaded one at a time.
- Lines 17-18 determine the full file name and perform the actual save.
- Lines 20-23 render a json response to the client. Note that line 20 contains a content type of “text/plain” instead of the anticipated “application/json”. As the FAQ states, IE displays a download dialog if the content type is “application/json” as it originally should have been. I didn’t notice this on IE9, but perhaps in an earlier IE this is the case.
Note that the ‘file’ reference in line 16 references a Stream, so you don’t necessarily have to save the file before processing it. You can just use a StreamReader or the like and process the data (thus you can remove lines 12-18 completely if you choose to do so).
There are some other Options you can tweak detailed on this page. More importantly, the options page also documents many callback events which can be used. For example, if you’d like to intercept when a file is selected, you may want to override the default implementation of the ‘add’ operation. As the documentation states, ‘add’ defaults to submitting the data using ‘data.submit( )’. But you might want to perform a validation on the selected file, for example, that the file extension is correct. So your implementation may look like this:
Although the documentation states that it’s possible to block the file selection type using “accept=’image/png’ “, I preferred to test it myself and to provide a validator-like error message. Note that this example also addresses an actual issue I came across: when I tried to use the good old validators on the file element as I always did (for example when a file selection is mandatory, or to test the file extension), they did not work as expected. It’s as if the ‘onchange event life-cycle’ was faulty when it came to the ‘add’ callback. That’s why I reverted to a Label and to checking the file extension “manually” as shown above. Another alternative may be to instruct the upload plug-in not to start uploading the files immediately, and to validate when the user manually clicks on a certain “upload” button.
One other feature I found to be useful, is the ability to upload not only the file, but also plain data, either form element values (which is the default), or just custom data. Consider the options to upload with the file user related data, or just plain context data. I used to do this using the query string, but now this can be easily done using the ‘formData’ callback. Here’s the client code:
Here’s the server side code change:
Uploaded file size limitations
Last but not least, in ASP.NET the upload max default is 4096K, which means that you are limited in file upload size. You can change that easily in the web.config using the maxRequestLength:
If you have no control over this, you may want to check the maxChunkSize plugin option, which should be able to upload your files in chunks.
No doubt that file uploading in the web has come a long way since the days of Classic ASP and even ASP.NET’s FileUpload control. Thumbs up for Sebastian Tschan on his excellent File Upload plugin which seems to provide lots and lots of features (most of which weren’t discussed in this post at all, but are detailed here, such as resume upload and cancel upload).