ASP.NET MVC DefaultModelBinder
转载自
Leo‘s Blog
看到很多ASP.NET MVC项目还在从request.querystring或者formContext里面获取数据,这实在是非常落后的做法。也有的项目建了大量的自定义的modelbinder,以为很牛,实际上也落后的很。
ASP.NET MVC提供了IModelBinder的默认实现,这个实现的类就叫DefaultModelBinder。我们在写代码的时候,几乎感觉不到这个类的存在,因为这个类自动将request信息解析成action参数。本文将向大家展示这个类是多么强大,可以拯救大量的代码。
先看个例子。假如有如下表单,用于编辑用户信息以及该用户的时间表。在这个例子中,我要利用DefaultModelBinder自动将整个表单数据解析成复杂实体类的实例。一行手工解析的C#代码都不用写。

对应的controller的代码如下,很简单:
1 public class DemoController : PublicControllerBase
2 {
3 public ActionResult UserEditor()
4 {
5 return View();
6 }
7
8 [HttpPost]
9 public string SaveUser(DemoUser user)
10 {
11 var result = string.Empty;
12 if (user != null)
13 {
14 result = Serializer.ToJson(user);
15 }
16 return result;
17 }
18 }
相关的实体类的定义,也很简单:
public class DemoUser
{
public string Username { get; set; }
public string Email { get; set; }
public string Language { get; set; }
public Gender Gender { get; set; }
public int[] RoleIds { get; set; }
public List<ScheduledJob> Jobs { get; set; }
}
public class ScheduledJob
{
public string Job { get; set; }
public string From { get; set; }
public string To { get; set; }
}
public enum Gender
{
Unknown = 0,
Male = 1,
Female = 2
}
请注意SaveUser这个action的参数,一个比较复杂的实体类的对象。DefaultModelBinder会自动将这个复杂的表单解析出来。这个保存的action将参数user直接序列化JSON字符串返回到浏览器。
下面看看HTML和JS。
HTML:
1 <form id="formUserEditor" action="/demo/saveuser" method="POST"> 2 <table class="form"> 3 <colgroup> 4 <col width="100"/> 5 <col width="auto"/> 6 </colgroup> 7 <tbody> 8 <tr> 9 <td>用户名:</td> 10 <td> 11 <input id="txtUsername" type="text" name="username" /> 12 </td> 13 </tr> 14 <tr> 15 <td>Email:</td> 16 <td> 17 <input id="txtEmail" type="text" name="email" /> 18 </td> 19 </tr> 20 <tr> 21 <td>语言:</td> 22 <td> 23 <select id="ddlLanguages" name="language"> 24 <option value="zh-cn">中文</option> 25 <option value="en-us">英文</option> 26 </select> 27 </td> 28 </tr> 29 <tr> 30 <td>性别:</td> 31 <td id="genders"> 32 <input type="radio" name="gender" value="@(Taoad.Web.Publics.Controllers.Gender.Unknown)" id="rdUnknown" /> 33 <label for="rdUnknown">未知</label> 34 35 <input type="radio" name="gender" value="@(Taoad.Web.Publics.Controllers.Gender.Male)" id="rdMale" /> 36 <label for="rdMale">男</label> 37 38 <input type="radio" name="gender" value="@(Taoad.Web.Publics.Controllers.Gender.Female)" id="rdFemale" /> 39 <label for="rdFemale">女</label> 40 </td> 41 </tr> 42 <tr> 43 <td>角色:</td> 44 <td id="roles"> 45 <input type="checkbox" name="roleids" value="1" id="cb1" /> 46 <label for="cb1">管理员</label> 47 48 <input type="checkbox" name="roleids" value="2" id="cb2" /> 49 <label for="cb2">部门经理</label> 50 51 <input type="checkbox" name="roleids" value="3" id="cb3" /> 52 <label for="cb3">客户</label> 53 </td> 54 </tr> 55 <tr> 56 <td>时间:</td> 57 <td> 58 <ul id="jobs"> 59 60 </ul> 61 <input type="button" value="添加" id="btnAddJob"/> 62 </td> 63 </tr> 64 <tr> 65 <td></td> 66 <td> 67 <input type="button" value="保存" id="btnSave"/> 68 <input type="button" value="取消" id="btnCancel" /> 69 </td> 70 </tr> 71 </tbody> 72 </table> 73 </form> 74 <hr/> 75 <div id="json"> 76 77 </div>
JS:
1 <script language="javascript" type="text/javascript">
2 $(document).ready(function () {
3 $("#btnAddJob").click(function() {
4 var $newLi = $(html);
5 $("#jobs").append($newLi);
6 bindLi($newLi);
7 });
8
9 $("#btnSave").click(function() {
10 var data = $("#formUserEditor").serialize();
11 $("#jobs li").each(function(i) {
12 var prefix = "&jobs[" + i + "]";
13 data += prefix + ".job=" + $(this).find(".job-id").val();
14 data += prefix + ".from=" + $(this).find(".job-from").val();
15 data += prefix + ".to=" + $(this).find(".job-to").val();
16 });
17 demo.ajax.post("/demo/saveuser", data, function(json) {
18 $("#json").html(json);
19 });
20 });
21 });
22
23 function bindLi(li) {
24 $(li).find(".btn-add").click(function () {
25 var $li = $(this).closest("li");
26 var $newLi = $(html);
27 $li.after($newLi);
28 bindLi($newLi);
29 });
30 $(li).find(".btn-delete").click(function () {
31 $(this).closest("li").remove();
32 });
33 }
34
35 var html = '<li>\
36 <select class="job-id">\
37 <option value="job1">工作1</option>\
38 <option value="job2">工作2</option>\
39 </select>\
40 <input type="text" placeholder="开始时间" class="job-from"/>\
41 ————\
42 <input type="text" placeholder="结束时间" class="job-to"/>\
43 <a href="javascript:void(0);" class="btn-add">添加</a> |\
44 <a href="javascript:void(0);" class="btn-delete">删除</a>\
45 </li>';
46 </script>
如果是如下的表单数据:

点击保存之后,返回的JSON数据为:

可以看到所有的表单数据都保存成功了。
再看看request信息:

请注意content-type的值。
实际上,POST到服务器的表单数据只是一个字符串,如下:

复制出来就是下面这样的字符串:
ajax=true&username=leo&email=leo%40gmail.com&language=zh-cn&gender=Unknown&roleids=1&roleids=3&jobs[0].job=job1&jobs[0].from=9:00&jobs[0].to=10:00&jobs[1].job=job2&jobs[1].from=10:00&jobs[1].to=11:00
由此可知,可用JS来拼接字符串,将整个表单通过键值对的形式序列化成一个字符串,再将该字符串传到服务器,这时DefaultModelBinder就可以自动解析实体类了。
关键点在于,对于List或者数组类型的数据,要加上数组下标。这样,任意复杂的数据结构,DefaultModelBinder都可以自动解析了。
思考一:
如果表单数据的键带有”demouser”的前缀,如下所示,那么这个action的参数还能自动解析吗?
Demouser.username=leo&demouser.email=leo@gmail.com&demouser.jobs[0].job=job1&.....
Action如下:
1 [HttpPost]
2 public string SaveUser(DemoUser user)
3 {
4 var result = string.Empty;
5 if (user != null)
6 {
7 result = Serializer.ToJson(user);
8 }
9 return result;
10 }
如果action参数名称又改成demouser呢?
思考二:
如果将action的参数名称改成如下代码所示,那么是否可以自动解析?(表单数据不带”demouser”前缀)
ajax=true&username=leo&email=leo%40gmail.com&language=zh-cn&gender=Unknown&roleids=1&roleids=3&jobs[0].job=job1&jobs[0].from=9:00&jobs[0].to=10:00&jobs[1].job=job2&jobs[1].from=10:00&jobs[1].to=11:00
Action如下:
1 [HttpPost]
2 public string SaveUser(DemoUser demoUser)
3 {
4 var result = string.Empty;
5 if (demoUser != null)
6 {
7 result = Serializer.ToJson(demoUser);
8 }
9 return result;
10 }
思考三:
如果action的参数是用另一个类包起来了,如下代码所示,那么表单数据应该是怎么样的字符串才能使DefaultModelBinder可以自动解析?
1 public class UserEditorViewModel
2 {
3 public DemoUser DemoUser { get; set; }
4 }
5
6 [HttpPost]
7 public string SaveUser(UserEditorViewModel model)
8 {
9 var result = string.Empty;
10 if (model != null)
11 {
12 result = Serializer.ToJson(model);
13 }
14 return result;
15 }
思考四:
如果action是这样定义的,那么表单数据的字符串又该怎么拼?
1 [HttpPost]
2 public string SaveUser(DemoUser demoUser, List<ScheduledJob> scheduledJobs)
3 {
4 var result = string.Empty;
5 if (demoUser != null)
6 {
7 demoUser.Jobs = scheduledJobs;
8 result = Serializer.ToJson(demoUser);
9 }
10 return result;
11 }


浙公网安备 33010602011771号