Uploading a file by .ajax() (jQuery) with MVC 3 using FormData

In one of my projects, I ran across a scenario in which I needed to upload a file and store it in a database. Sounds simple enough, right? Well, I like to be bit masochistic as it turns out, and I developed a heavy AJAX use application for performance and caching reasons. By default, AJAX calls are not designed and will not include file input types as post data. Everything else, sure, just not files, which kind of sucks when my AJAX heavy application required its use.

Luckily, there were a couple of ways around this. You can either create an iframe which gets passed the information for the upload or you can use the HTML 5 FormData function call. The former does have limitations with callback functionality, however is more widely supported. The latter won’t work if you are using Internet Explorer. Fortunately, since my application will only be used by browsers supporting HTML 5 functionality, including XMLHttpRequest Level 2, it turns into a non-issue, at least for now. You can check and see if your browser supports that functionality as well.

So like regular file uploads, AJAX file uploads using FormData aren’t much different, you have to set up your view file to have a form which supports multipart/formdata encoding and you have to have an input type of file with a name attribute. Pretty simple.

In the view (create):

@model HIDDEN.Models.doc_screenshot

Create

@using (Html.BeginForm("Create", "docScreenshot", FormMethod.Post, new { enctype="multipart/form-data"}))
{
    @Html.ValidationSummary(true)

        Add a screenshot


            @Html.LabelFor(model => model.name)


            @Html.EditorFor(model => model.name)
            @Html.ValidationMessageFor(model => model.name)


        @Html.HiddenFor(model => model.lineItemID, "doc_lineItem")





}

The controller remains the same as well. In my project, I am receiving the function parameters as a Model type using JSON (which I enable in my main application by adding the JsonValueProviderFactory), therefore I actually have to find the file request and store it in a temporary variable. I accomplish this using the Request.Files[] list as shown below. Just to alleviate some confusion: I am using a custom Json response class in order to return debug or error information; replace with your own implementation.

In the controller:

[HttpPost]
public ActionResult Create(doc_screenshot doc_screenshot)
{
    JsonResponse res = new JsonResponse(); //I use a custom JsonResponse Class to send back error/debugging info
    HttpPostedFileBase file = Request.Files["upimage"] as HttpPostedFileBase; //the "name" attribute of the file input, in this case "upimage"
    if (file == null)
    {
        res.Status = Status.Error;
        res.Message = "You need to add an actual image.";
        return Json(res);
    }
    Int32 length = file.ContentLength; //get the file length
    byte[] tempImage = new byte[length]; //apply the length to the new variable so we can copy contents
    file.InputStream.Read(tempImage, 0, length); //grab file stream contents and store them in temp variable
    // You will probably want to add some checks here for filetype to make sure that you aren't getting something you don't want
    doc_screenshot.image = tempImage; //copy the image over to the model
    if (ModelState.IsValid)
    {
        res.Message = "Successfully created new screenshot";
        res.Status = Status.Ok;
        db.doc_screenshot.Add(doc_screenshot);
        try
        {
            db.SaveChanges();
            res.Id = doc_screenshot.id;
        }
        catch (Exception e)
        {
            res.Message = e.Message;
            res.Status = Status.Error;
        }
    }
    else
    {
        res.Message = "Model State error.";
        res.Status = Status.Error;
    }
    return Json(res);
}

Alright, basics out of the way, now we get to the FormData. In the javascript section of my code, I prevent the form from being submitted when the user hits the “upload” button and instead inject my own AJAX functionality. I detect whether the form that is submitted has enctype of “multipart/form-data” and create a new FormData string if it does. If not, I treat it as a standard AJAX form. Oh and since I’m using the dialog jQuery UI component, I make all calls to the form by using “#dialog form” - replace this with your actual form name in your own implementation.

In the javascript

$("#dialog form").submit(function (event) {
    event.preventDefault();
    action = $("#dialog form").attr("action");
    if ($("#dialog form").attr("enctype") == "multipart/form-data") {
        //this only works in some browsers.
        //purpose? to submit files over ajax. because screw iframes.
        //also, we need to call .get(0) on the jQuery element to turn it into a regular DOM element so that FormData can use it.
        dataString = new FormData($("#dialog form").get(0));
        contentType = false;
        processData = false;
    }
    else {
        // regular form, do your own thing if you need it
    }
    $.ajax({
        type: "POST",
        url: action,
        data: dataString,
        dataType: "json", //change to your own, else read my note above on enabling the JsonValueProviderFactory in MVC
        contentType: contentType,
        processData: processData,
        success: function (data) {
           //BTW, data is one of the worst names you can make for a variable
        },
        error: function(jqXHR, textStatus, errorThrown)
        {
            //do your own thing
        }
    });
    $("#dialog").dialog("close");
}); //end .submit()

And that should get you rolling with using FormData with your MVC application for uploading files. The last resource I’d recommend if you need an alternative is using the uploadify jQuery plugin.