Ajax file upload

Sometimes it would be useful to upload a file to a website while the rest of the page remains unchanged, so with Ajax. I’ve got bad news for you: the Level 1 version of the XMLHttpRequest object doesn’t support that, so if you must support older browsers, you need some tricks.

One of the most widely used tricks is using an iframe, however in that case the results from the server (for example the validation errors) are also delivered to the iframe. So if you work with a hidden iframe and you need the result in client-processable way in JavaScript, you have to dig it from the iframe. Fortunately the jQuery Form Plugin can help you to solve these issues.

First, create a view-model on the server, in which a HttpPostedFileBase property represents the uploaded file. I’ve also added a Name property for demonstration purposes:

public class UploadVM
    [Required( ErrorMessage = "Please enter a name!" )]
    public string Name { get; set; }

    public HttpPostedFileBase Attachment { get; set; }

The [Attachment] is the file validation attributed I presented in an earlier post. It checks whether the file is submitted, and also checks the extension and the size of the file.

For this model you can create a form that submits the file and the provided name:

@using( Html.BeginForm( "Index", "Home", FormMethod.Post, 
        new { id = "myForm", enctype = "multipart/form-data" } ) )
        <label for="txtName">Name:</label>
        <input type="text" id="txtName" name="Name" />

        <label for="fupAttachment">File:</label>
        <input type="file" id="fupAttachment" name="Attachment" />

        <input type="submit" value="Upload" />

    <div id="errors"></div>

It’s important that the form must have an enctype attribute with multipart/form-data value, because that supports file upload. I also created a div with errors id, which will host the error messages.

This form posts to the Index action of the HomeController which can be implemented like this:

public ActionResult Index( UploadVM model )
  if( !this.ModelState.IsValid )
    string firstError = ModelState.First( m => m.Value.Errors.Any() )
.Value.Errors[ 0 ].ErrorMessage;
return this.FileUploadFailure( firstError ); } // Process the file here string message = String.Format( "The file '{0}' is successfully uploaded.",
model.Name );
return this.FileUploadSuccess( message ); }

If there is any validation error in the view-model, the runtime adds them to the ModelState thanks to the attributes. Just as always the ModelState is check in the beginning of this method, and if there is any error an error message is returned, if not, then the file is processed and a success message is returned to the client.

The returned value is a JSON object, because that comes really handy on the client. But don’t forget, that it will land in an iframe, and not every browser tolerates an application/json result in iframes. Fortunately the jQuery Form Plugin supports a simple hack: just wrap the result into a <textarea> element and send it back in a text/html response, the plugin will do the rest.

To simplify (and standardize) this wrapping, you can create a custom result type:

public class FileUploadJsonResult : JsonResult
  public override void ExecuteResult( ControllerContext context )
    this.ContentType = "text/html";
    context.HttpContext.Response.Write( "<textarea>" );
    base.ExecuteResult( context );
    context.HttpContext.Response.Write( "</textarea>" );

You can also create extension methods for the Controller class to create these upload results just like any other result from the action methods:

public static FileUploadJsonResult FileUploadSuccess( 
this Controller controller, string successMessage = null ) { return new FileUploadJsonResult { Data = new { Success = true, Message = successMessage } }; } public static FileUploadJsonResult FileUploadFailure(
this Controller controller, string errorMessage ) { return new FileUploadJsonResult { Data = new { Success = false, Message = errorMessage } }; }

Here you can create a custom object with any structure in the Data property that will be delivered to the client in JSON. The code above just signals the result of the file upload in the Success property, and returns a detailed status message in the Message property that can be displayed by the browser.

So now you have fully functional server side: a form that POSTs to an action which validates the input, and if every field is valid, processes the values and returns a status in a JSON object.

Let’s implement the client side, of course with the jQuery Form Plugin. As its name implies, this is a jQuery plugin that you can apply to the jQuery object which wraps the form element (the $form in this case):

    iframe: true,
    dataType: "json",
    beforeSubmit: function () {
}, success: function (result) { // TODO
}, error: function () { // TODO
} });

You usually have to implement the following callback functions:

  • beforeSubmit: this is called before the POST is sent to the server. This is where you can display a “please wait” message, a progress indicator, or simply block the form controls using the jQuery BlockUI plugin.
  • success: this is called when the upload is completed successfully. At least in theory. In my experience this callback is also called when the server returns some low-level error. If there was no error, you receive the response from the server in the function parameter in JSON, but be prepared, that in case of error it can be undefined as well!
  • error: this is called when an error occurred during postback.

Here is an example of a working success callback implementation:

if (!result) {
  $errors.html('<div class="validation-summary-errors"><ul><li>Oooops....
); } else { $form.resetForm(); if (result.Success === true) { var message = result.Message; if (message && message.length > 0) { $errors.html( message ); } } else { $errors.html('<div class="validation-summary-errors"><ul><li>{0}
.format(result.Message)); } }

In this code the errors are displayed in a complex HTML fragment. The reason for that is that the same HTML fragment is generated by the standard ASP.NET MVC validation helpers, so with this code our Ajax errors will be rendered with the same style.

The format function acts like the String.Format method on the server and can be implemented like this:

String.prototype.format = function () {
  var args = arguments;
  return this.replace(/\{\{|\}\}|\{(\d+)\}/g, function (m, n) {
    if (m === "{{") { return "{"; }
    if (m === "}}") { return "}"; }
    return args[n];

If you return all ModelState errors from the server, you can render them in a loop.

You can download the full source code with tons of comment from the following link: http://sdrv.ms/10QQLSp


Technorati-címkék: ,

2 thoughts on “Ajax file upload

  1. Eprofit Academy Reviews

    Hello! I know this is kind of off-topic but I had to ask.
    Does operating a well-established blog such as yours take a large amount of work?

    I’m brand new to operating a blog but I do write in my diary on a daily basis. I’d like
    to start a blog so I can easily share my own experience and thoughts online.
    Please let me know if you have any kind of ideas or tips
    for brand new aspiring blog owners. Appreciate it!

  2. dobry router gsm

    I am extremely inspired along with your writing abilities as
    well as with the format in your weblog. Is this a paid subject matter or did you modify it your self?
    Anyway stay up the excellent high quality writing, it’s uncommon to peer a nice weblog like this one nowadays..


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s