Anyone who's been involved in an ASP.NET MVC project that is quite Ajax heavy will probably have noticed that something was always missing. Imagine this front end scenario,
<% using (Html.BeginForm()) { %>
<p>
<%=Html.LabelFor(m => m.Username)%>
<%=Html.TextBoxFor(m => m.Username)%>
</p>
<p>
<%=Html.LabelFor(m => m.Password)%>
<%=Html.TextBoxFor(m => m.Password)%>
</p>
<%} %>
<script type="text/javascript">
$("form").submit(function (evt) {
// extract values to submit
var form = $(this),
username = form.find("[name=Username]").val(),
password = form.find("[name=Password]").val(),
json = JSON.stringify({
Username: username,
Password: password
});
$.ajax({
url: form.attr("action"),
type: 'POST',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
data: json,
success: handleLogin
});
// stop form submitting
evt.preventDefault();
});
</script>
Which posts to the following action
[HttpPost]
public ActionResult Index(LoginModel model)
{
// do login
return Json(new
{
Success = true
});
}
We have a login screen that is submitted via ajax. Now this is quite a contrived example (ideally you'd be performing a normal post via ajax in this situation) but there are many instances where this sort practise would apply (ExtJS' RESTful DataWriters for example).
MVC 2
In MVC 2 this wouldn't work immediately. The default model binder in MVC 2 uses Request parameters to bind to model properties but in this case there are none as the ajax content is the body of the request.
To accommodate this sort of request in MVC 2 we had to provide a custom model binder that knows how to deal with JSON requests,
public class JsonModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
HttpRequestBase request = controllerContext.HttpContext.Request;
string jsonStringData = new StreamReader(request.InputStream).ReadToEnd();
return JsonConvert.DeserializeObject(jsonStringData, bindingContext.ModelType);
}
}
And attribute our actions model argument telling it to use this binder,
[HttpPost]
public ActionResult Index([ModelBinder(typeof(JsonModelBinder))]LoginModel model)
{
// ...
}
It'll do the job but it's incredibly messy.
MVC 3
MCV3 fills this gap thanks to the JsonValueProviderFactory. The JVPF operates at a higher level than a model binder. Basically what it does when a JSON request is received is that it pulls the values out of the JSON body as key value pairs which means they are available to the model binders including the default model binder. No special wiring required, no custom model binders (unless of course you want one) just out-of-the-box workingness!
[HttpPost]
public ActionResult Index(LoginModel model)
{
// do login
return Json(new
{
Success = true
});
}
I know most of this post was taken up by MVC2 specific implementation but isn't that really the point? MVC3 is a nice refinement of MVC2 there isn't anything new exactly but the core stuff that is there has been made easier and more configurable.