Introduction
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<string> Post(Dummy value)
{
return new string[] { value.Value1, value.Value2 };
}
I noticed that even 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 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 that was 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<t>(MediaTypeFormatter formatter, string str) where T : class
{
Stream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(str);
writer.Flush();
stream.Position = 0;
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 making 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. Sometimes, making silly errors can drive you into learning really interesting things.
Useful References
CodeProject