Friday, November 8, 2013

$.ajax and javascript's FormData object

Wow...I am a slow study when it comes to JavaScript.  I guess there's nothing really hard about it - it's just that I have a real firm distaste for loosely typed objects and variables.  The fact you can add a method to any  variable and not have it complain to you until it is running is just plan frustrating.  I even swear that I've seen code work the first time, the second, and them for some inexplicable reason stop working, only to turn around and work again!  The thing that bugs me the most is how differently each of the browsers handle JavaScript.  jQuery (and others) attempts to decouple the developer's need to know the browser behaviors and does it reasonably well from what I've seen thus far (except of course for IE).  I'm sure as I play with it more that I'll get used to working with JavaScript.

But today...wow...I was stumped with a jQuery ajax call to a Microsoft MVC controller method.  Hopefully this might help you if you run into problems trying to send form data that contains file input types.

First - you really really don't need to use the append() method to add files to the FormData class.  Most of the searches that I did made the statement that in order for this work you needed to use the append() method to attach the file to the form being posted to the web server.  I found that simply calling the constructor of the FormData object with the form element as a parameter will pull in all the necessary data you need.  The constructor not only included data from the elements on the form, but also any input files the user has added as illustrated below.

var formData = new formData($('form')[0]);  

Yes that's all that is needed.  Not being that close to the development/history of JavaScript this could have been different in earlier versions - but as of this writing this is all I needed.

Second - the tricky part and something that took me the better part of a morning to figure out.  The $.ajax() method can in fact sent over the FormData object in a format that can be de-serialized by the  MVC Razor engine into your model.  Here's the code:
$('form').on('submit', function(e) {  
   
   $.ajax({  
     type: this.method,  
     // this works!...dno't mess with it...  
     url: '@Url.Action("SaveOffer", "Offer")',  
     contentType: false,  
     processData: false,  
     data: new FormData($('form')[0]),  
The key to doing this is the contentType and processData properties in the ajax method.  Setting these two properties, along with overriding the url: property to the method (below) in your controller that will accept and process the form data.

 [HttpPost]  
 public JsonResult SaveOffer(OfferModel offerModel)  
 {  
   try  
    {  
      if (ModelState.IsValid)  
      {  
        GetFiles(ref offerModel);  
        _rewardsAdminRepository.UpdateOffer(offerModel);  
   
        return Json(new { result = "ok" },  
         JsonRequestBehavior.AllowGet);  
      }  
      return Json(new { result = "error" },   
         JsonRequestBehavior.AllowGet);  
    }  
When the ajax method invokes the controller's SaveOffer method the Razor engine was able to take the content within the FormData object and fully hydrate the OfferModel.  Once I figured out those two properties where the key I was successfully saving data entered on the form as well as uploading any files attached to the post.