Friday, November 29, 2013

Public field not bound when posting to WebAPI (or a deep dive into WebAPI model binding)

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 IEnumerable Post(Dummy value)
{
 return new string[] { value.Value1, value.Value2 };
}
I noticed that even that the instance of Dummy was being created, 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 Deserialize(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;
}
passing 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: 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 body
If 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


Unknown

Software developer with main experience in Microsoft Technologies but who also enjoys working with other languages. More about me

0 comentarios:

Post a Comment

 

Copyright @ 2013 A learning journey.