ASP.NET MVC 3 introduced the ability to bind an incoming JSON request to an action method parameter, which is something I wrote about before. For example, suppose you have the following class defined (keeping it really simple here):

public class ComicBook {
public string Title { get; set; }
public int IssueNumber { get; set; }
}

And you have an action method that accepts an instance of ComicBook:

[HttpPost]
public ActionResult Update(ComicBook comicBook) {
// Do something with ComicBook and return an action result
}

You can easily post a comic book to that action method using JSON.

Under the hood, ASP.NET MVC uses the DefaultModelBinder in combination with the JsonValueProviderFactory to bind that value.

A question on an internal mailing list recently asked the question (and I’m paraphrasing here), “Why not cut out the middle man (the value provider) and simply deserialize the incoming JSON request directly to the model (ComicBook in this example)?”

Great question! Let me provide a bit of background to set the stage for the answer.

Posting Content to an Action

There are a couple of different content types you can use when posting data to an action method.

application/x-www-form-urlencoded

You may not realize it, but when you submit a typical HTML form, the content type of that submission is application/x-www-form-url-encoded.

As you can see in the screenshot below from Fiddler, the contents of the form is posted as a set of name value pairs separated by ampersand characters. The name and value within each pair are separated by an equals sign.

By the time you typically interact with this data (outside of model binding), it’s in the form of a dictionary like interface via the Request.Form name value collection.

The following screenshot shows what such a request looks like using Fiddler.

When content is posted in this format, the DefaultModelBinder calls into theFormValueProvider asking for a value for each property of the model. TheFormValueProvider is a very thin abstraction over the Request.Form collection.

application/json

Another content type you can use to post data is application/json. As you might guess, this is simply JSON encoded data.

Here’s an example of a bit of JavaScript I used to post the same content as before but using JSON. Note that this particular snippet requires jQuery and a browser that natively supports the JSON.stringify method.

<script type="text/javascript">
$(function() {
var comicBook = { Title: "Groo", IssueNumber: 101 }
var comicBookJSON = JSON.stringify(comicBook);
$.ajax({
url: '/home/update',
type: 'POST',
dataType: 'json',
data: comicBookJSON,
contentType: 'application/json; charset=utf-8',
});
});
</script>

When this code executes, the following request is created.

Notice that the content is encoded as JSON rather than form url encoded.

JSON is a serialization format so it’s in theory possible that we could straight deserialize that post to a ComicBook instance. Why don’t we do that? Wouldn’t it be more efficient?

To understand why, let’s suppose we did use serialization and walk through a common scenario. Suppose someone submits the form and they enter a string instead of a number for the field IssueNumber. You’d probably expect to see the following.

Notice that the model binding was able to determine that the Title was submitted correctly, but that the IssueNumber was not.

If our model binder deserialized JSON into a ComicBook it would not be able to make that determination because serialization is an all or nothing affair. When serialization fails, all you know is that the format didn’t match the type. You don’t have access to the granular details we need to provide property level validation. So all you’d be able to show your users is an error message stating something went wrong, good luck figuring out what.

The Solution

Instead, what we really want is a way bind each property of the model one at a time so we can determine which of the fields are valid and which ones are in error. Fortunately, the DefaultModelBinder already knows how to do that when working with the dictionary-like IValueProvider interface.

So all we need to do is figure out how to expose the posted JSON encoded content via the IValueProvider interface. As I wrote before, Jonathan Carter had the bit of insight that provided the solution to this problem. He realized that you could have the JSON value provider deserialize the incoming JSON post to a dictionary. Once you have a dictionary, it’s pretty easy to implementIValueProvider and the DefaultModelBinder already knows how to bind those values to a type while providing property level validation. Score!

Value Provider Aggregation

The answer I provided only tells part of the story of why this is implemented as a value provider. There’s another aspect that was illustrated by my co-worker Levi. Sadly, for someone so gifted intellectually, he has no blog, so I’ll paraphrase his words here (with a bit of verbatim copying).

As I mentioned earlier, value providers provide an abstraction over where values actually come from. Value providers are responsible for aggregating the values that are part of the current request, e.g. from Form collection, the query string, JSON, etc.  They basically say “I don’t know what a ‘FirstName’ is for or what you can do with it, but if you ask me for a ‘FirstName’ I can give you what I have.”

Model binders are responsible for querying the value providers and building up objects based on those results.  They basically say “I don’t know where directly to find a ‘FirstName’, ‘LastName’, or ‘Age’, but if the value provider is willing to give them to me then I can create a Person object from them.”

Since model binders aren’t locked to individual sources (with some necessary exceptions, e.g. HttpPostedFile), they can build objects from an aggregate of sources. If your Person type looks like this:

public class Person {
int Id { get; set; }
[NonNegative]
int Age { get; set; }
string FirstName { get; set; }
string LastName { get; set; }
}

And a client makes a JSON POST request to an action method (say with the url/person/edit/1234 with the following content:

{
"Age": 30,
"FirstName": "John",
"LastName": "Doe"
}

\ The DefaultModelBinder will pull the Id value from the RouteData and theAgeFirstName, and LastName values from the JSON when building up the Personobject. Afterwards, it’ll perform validation without having to know that the various values came from different sources.

Even better, if you wrote a custom Person model binder and made it agnostic as to the current IValueProvider, you’d get the correct behavior on incoming JSON requests without having to change your model binder code one tiny iota.  Neither of these is possible if the model binder is hard-coded to a single provider.

TL;DR Summary

The goal of this post was to provide a bit of detail around an interesting aspect of how ASP.NET MVC turns strings sent to a web server into strongly typed objects passed into your action methods.

Going back to the original question, the answer is simply, we use a value provider for JSON to enable property level validation of the incoming post and also so that model binding can build up an object by aggregating multiple sources of data without having to know anything about those sources.

我的理解:Value Provider 聚合了当前请求的数据,并以特有方式组织数据,提供了更细粒度的请求数据管理,Model binders更加容易从Value Provider中获取想要的数据,以便组合成有意义的input model(这里区分view model,domain model)

What’s the Difference Between a Value Provider and Model Binder?的更多相关文章

  1. [引]ASP.NET MVC 4 Content Map

    本文转自:http://msdn.microsoft.com/en-us/library/gg416514(v=vs.108).aspx The Model-View-Controller (MVC) ...

  2. [翻译]Orchard如何工作

    Orchard一直是博主心中神一般的存在,由于水平比较菜,Orchard代码又比较复杂看了几次都不了了之了.这次下定决心要搞懂其工作原理,争取可以在自己的项目中有所应用.为了入门先到官网去学习一下相关 ...

  3. [ASP.NET MVC 小牛之路]09 - Controller 和 Action (1)

    我们知道,在 MVC 中每个请求都会提交到 Controller 进行处理.Controller 是和请求密切相关的,它包含了对请求的逻辑处理,能对 Model 进行操作并选择 View 呈现给用户, ...

  4. 【ASP.NET MVC 学习笔记】- 10 Controller和Action(1)

    本文参考:http://www.cnblogs.com/willick/p/3331521.html 1.继承IController接口,示例代码将当前请求的Controller和Action打印到浏 ...

  5. Controller 和 Action -1

    https://www.cnblogs.com/willick/p/3331521.html MVC 的每个请求都会提交到 Controller 处理.Controller 包含了对请求的逻辑处理,能 ...

  6. Android应用程序组件Content Provider的启动过程源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6963418 通过前面的学习,我们知道在Andr ...

  7. [Windows Azure] Building the web role for the Windows Azure Email Service application - 3 of 5

    Building the web role for the Windows Azure Email Service application - 3 of 5. This is the third tu ...

  8. 分享基于EF+MVC+Bootstrap的通用后台管理系统及架构

      基于EF+MVC+Bootstrap构建通用后台管理系统,集成轻量级的缓存模块.日志模块.上传缩略图模块.通用配置及服务调用, 提供了OA.CRM.CMS的原型实例,适合快速构建中小型互联网及行业 ...

  9. 仅此一文让你明白ASP.NET MVC 之Model的呈现(仅此一文系列三)

    本文目的 我们来看一个小例子,在一个ASP.NET MVC项目中创建一个控制器Home,只有一个Index: public class HomeController : Controller { pu ...

随机推荐

  1. Svn使用遇到的错误

    1. 权限不足导致的错误

  2. java代码从键盘输入次数,然后进行运算-----菜鸟如此菜

    package com.aaa; import java.util.Scanner; // //求两数相加的和.从键盘输入 public class ftwert { public static vo ...

  3. java中length的用法

    总结:length是属性...有很多种,不仅仅是指长度 package com.c2; import java.io.BufferedReader; import java.io.IOExceptio ...

  4. 命令行调用远程dubbo服务

    有时需要对dubbo服务做个简单的测试,或者想看下某个dubbo服务类所提供的方法,可以直接在命令行通过telnet的方式来查看和调用dubbo服务,方法如下: telnet 127.0.0.1 20 ...

  5. 严谨的程序案例Api

    文档 功能 同步推荐关系 接口方法 syncRelation 参数描述 OriginalUsername 查询的用户用户名 RecommandUsername 推荐人用户名 返回值 status 1成 ...

  6. 整合多个py文件接口的unittest。suite执行方法

    1.每个接口用例为一个.py文件.内容如下: getAdMakeMoneyList文件: # coding=utf-8import xlrdimport requestsimport unittest ...

  7. springboot成神之——Basic Auth应用

    本文介绍Basic Auth在spring中的应用 目录结构 依赖 入口DemoApplication 验证Authenication 配置WebSecurityConfig 控制器TestContr ...

  8. 用cascade删除有约束的表或记录

    删除有约束的表 Drop table TERMPRO_RULE_ROUTE_TYPE cascade constraints:

  9. Maven+Mybatis+Spring+SpringMVC实现分页查询

    转载:http://www.cnblogs.com/zhangtan/p/5846955.html 一.项目搭建 关于项目搭建,小宝鸽以前写过一篇Spirng+SpringMVC+Maven+Myba ...

  10. Spring Cloud Feign 1(声明式服务调用Feign 简介)

    Spring Cloud Feign基于Netflix Feign 同时整合了Spring Cloud Ribbon和Spring Cloud Hytrix,除了提供两者的强大功能外,它还提供了一种声 ...