When trying to create a sandbox project using WebAPI (on MVC4), I was struggling with a weird problem: My data wasn't being received in the server. I had the following jQuery call
$.post("api/Values", {value1:1, value2:2}, function(result){ console.log(result); })and the WebAPI service action that I was targeting was something like this
public IEnumerableI noticed that even that the instance of Dummy was being created,Post(Dummy value) { return new string[] { value.Value1, value.Value2 }; }
Value1
and Value2
where always null. The Dummy class was
public class Dummy { public string Value1; public string Value2; }Pretty simple, right?. Well, after reading doing a lot of research, I changed by accident one of the
Dummy
fields to become a property
public class Dummy { public string Value1; public string Value2 {get;set;} }I tested again and VoilĂ !!... well, half voilĂ actually... When posting, now I was receiving data in
Value2
, but still not in Value1. This was really intriguing... how come property was being assigned correctly but not the field? Both are public, right? Why the difference?
Obviously, I knew the solution was changing both fields to be properties now, but I wanted to know why was that happening. I started digging on how WebAPI works and found a really interesting Web API poster, that describes the full lifecycle of a HTTP message. There I got my first clue, so I started researching on how ModelBinding happens. As described there, one of the binding methods is MediaTypeFormatter. Since I was sending JSON object, I tested the Deserialization process based on the test methods provided in the WebAPI overview site
T Deserializepassing the same JSON object that I had on my jQuery call. The result: The method assigned successfully the values for both the field and the property. By inspecting the HTTP Request headers, I found out that data wasn't being actually sent as JSON but in the following format:(MediaTypeFormatter formatter, string str) where T : class { // Write the serialized string to a memory stream. Stream stream = new MemoryStream(); StreamWriter writer = new StreamWriter(stream); writer.Write(str); writer.Flush(); stream.Position = 0; // Deserialize to an object of type T return formatter.ReadFromStreamAsync(typeof(T), stream, null, null).Result as T; }
Content-Type:application/x-www-form-urlencoded; charset=UTF-8
, which tells the server that data is being sent like this: Value1=1&Value2=2
. Then, we need to change the AJAX call to be like this
$.ajax({ url: "api/Values", data: JSON.stringify({Value1:1,Value2:2}), type: "POST", contentType:"application/json; charset=utf-8" })please notice 2 things: I changed the contentType for the request AND Stringified the JSON object. By doing these changes,
Dummy
public fields were now populated correctly.
Now, I still wanted to know why my values weren't bound when I wasn't specifying the request content type. Doing more research, I found this really interesting article by Mike Stall called How WebAPI does parameter binding which states
There are 2 techniques for binding parameters: Model Binding and Formatters. In practice, WebAPI uses model binding to read from the query string and Formatters to read from the bodyIf you are not yet bored, you might remember that when we didn't specify the request content type, the data was being sent as
Content-Type:application/x-www-form-urlencoded; charset=UTF-8
. This means, that WebAPI was using ModelBinding (and not formatters) to populate the Dummy
instance. Moreover, the article has another interesting declaration:
ModelBinding is the same concept as in MVC, [...]. Basically, there are “ValueProviders” which supply pieces of data such as query string parameters, and then a model binder assembles those pieces into an object.And how does ModelBinding work in MVC? That was my next question. And I was really happy that Microsoft open-sourced the ASP.Net WebStack, because there is where we can find the answer. If we look into DefaultModelBinder source code, we'll find that when talking about complex models, it only looks for the object properties to populate the data (maybe because having public fields is a bad practice).
Well, I hope you can find this post as interesting as I found learning all this. Some times making silly errors can drive you into learn really interesting things.
Useful references
- http://blogs.msdn.com/b/jmstall/archive/2012/04/16/how-webapi-does-parameter-binding.aspx
- http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
- http://www.asp.net/web-api/overview/formats-and-model-binding/json-and-xml-serialization
- http://www.asp.net/web-api/overview/formats-and-model-binding/media-formatters
- http://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/DefaultModelBinder.cs
- http://api.jquery.com/jQuery.ajax/
0 comentarios:
Post a Comment