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 }

ASP.NET MVC DefaultModelBinder的更多相关文章
- 你从未知道如此强大的ASP.NET MVC DefaultModelBinder
看到很多ASP.NET MVC项目还在从request.querystring或者formContext里面获取数据,这实在是非常落后的做法.也有的项目建了大量的自定义的modelbinder,以为很 ...
- asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证
原文:asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证 在前面的文章中我们曾经涉及到ControllerActionInvoker类GetPara ...
- ASP.NET MVC——模型绑定
这篇文章我们来讲讲模型绑定(Model Binding),其实在初步了解ASP.NET MVC之后,大家可能都会产生一个疑问,为什么URL片段最后会转换为例如int型或者其他类型的参数呢?这里就不得不 ...
- ASP.NET MVC Model验证(五)
ASP.NET MVC Model验证(五) 前言 上篇主要讲解ModelValidatorProvider 和ModelValidator两种类型的自定义实现, 然而在MVC框架中还给我们提供了其它 ...
- ASP.NET MVC Model验证(四)
ASP.NET MVC Model验证(四) 前言 本篇主要讲解ModelValidatorProvider 和ModelValidator两种类型的自定义实现,前者是Model验证提供程序,而Mod ...
- ASP.NET MVC Model验证(三)
ASP.NET MVC Model验证(三) 前言 上篇中说到在MVC框架中默认的Model验证是在哪里验证的,还讲到DefaultModelBinder类型的内部执行的示意图,让大家可以看到默认的M ...
- ASP.NET MVC Model验证(二)
ASP.NET MVC Model验证(二) 前言 上篇内容演示了一个简单的Model验证示例,然后在文中提及到Model验证在MVC框架中默认所处的位置在哪?本篇就是来解决这个问题的,并且会描述一下 ...
- ASP.NET MVC Model验证(一)
ASP.NET MVC Model验证(一) 前言 前面对于Model绑定部分作了大概的介绍,从这章开始就进入Model验证部分了,这个实际上是一个系列的Model的绑定往往都是伴随着验证的.也会在后 ...
- ASP.NET MVC Model绑定(一)
ASP.NET MVC Model绑定(一) 前言 ModelMetadata系列的结束了,从本篇开始就进入Model绑定部分了,这个系列阅读过后你会对Model绑定有个比较清楚的了解, 本篇对于Mo ...
随机推荐
- 关于IoCallDriver
通常我们所知IoCallDriver是把irp传递给下一层设备,传递到底是什么意思呢?IoCallDriver中实际调用了IopfCallDriver,其代码如下:NTSTATUSFORCEINLIN ...
- 算法导论(第三版)Exercies2.2(插入排序)
2.2-1: Θ (n3) 2.2-2:插入排序 void selectionSort(int a[], int n) { int i, j, k, key; ; i<n-; i++) { k ...
- Java并发实现一(并发的实现之Thread和Runnable的区别)
package com.subject01; public class ThreadOrRunnable { public static void main(String[] args) { Syst ...
- Spring 的优秀工具类盘点第 2 部分
特殊字符转义 由于 Web 应用程序需要联合使用到多种语言,每种语言都包含一些特殊的字符,对于动态语言或标签式的语言而言,如果需要动态构造语言的内容时,一个我们经常会碰到的问题就是特殊字符转义的问题. ...
- python lcd 时间显示
#!/usr/bin/python # QStopWatch -- a very simple stop watch # Copyright (C) 2006 Dominic Battre <d ...
- (转)Building MariaDB on Mac OS X using Homebrew
https://kb.askmonty.org/en/building-mariadb-on-mac-os-x-using-homebrew/ Work has been done to provid ...
- oracle插入特殊字符'&'问题
oracle转义字符开关:set define off / show define
- C++11中正則表達式測试
VC++2010已经支持regex了, 能够用来编译下述代码. #include <string> #include <regex> #include <iostream ...
- hdfs经常使用命令
hadoop hdfs经常使用命令 hadoop fs -ls /user/deploy/recsys/workspace/ouyangyewei 查看ouyangyewei文件夹文件 hadoop ...
- Android 关于倒计时功能的实现
关于倒计时的实现,可以说有很多的方法,比较常见的就是Timer+TimerTask+Handler了,或者还可以配合Runnable.例如下面的代码: import java.util.Timer; ...