模型绑定架构

本文总阅读量:
  1. 1. 模型绑定
    1. 1.1. 模型绑定的基础结构
    2. 1.2. 默认模型绑定器
      1. 1.2.1. 绑定简单类型
      2. 1.2.2. 处理可选值
      3. 1.2.3. 值提供程序和优先级
      4. 1.2.4. 绑定复杂类型
      5. 1.2.5. 绑定集合
      6. 1.2.6. 绑定带复杂类型的集合
      7. 1.2.7. 绑定上传的文件内容
    3. 1.3. 默认绑定器可以自定义的部分
      1. 1.3.1. 创建属性白名单
      2. 1.3.2. 创建属性黑名单
      3. 1.3.3. 加前缀的参数别名
  2. 2. 高级模型绑定

不怕慢,就怕停—–孔子

模型绑定

模型绑定是指将通过HTTP请求说提交的值绑定到控制器所用的参数的过程。

模型绑定的基础结构

模型绑定逻辑是封装在一个特殊的模型绑定器类中的。

1 分析方法的签名

调用程序会将控制器名称拓展成一个类名称,并将操作名称解析成控制器上的方法名称,然后,调用程序会收集进行方法调用所需的所有值,与此同时会查看方法的签名,试图找出签名中每个参数所需的输入值。

2 为类型获取绑定器

操作调用程序知道每个参数的名称及其类型之后,根据每一个参数,调用程序都会获得一个模型绑定器对象,将请求上下文中的值绑定到对应的参数上去。

ASP.NET MVC使用DefaultModelBinder类的默认模型绑定类,它实现IModelBinder接口

1
2
3
4
5
6
7
8
public interface IModelBinder
{
/// <summary>使用指定的控制器上下文和绑定上下文将模型绑定到一个值。</summary>
/// <returns>绑定值。</returns>
/// <param name="controllerContext">控制器上下文。</param>
/// <param name="bindingContext">绑定上下文。</param>
object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext);
}

默认模型绑定器

默认模型绑定器使用基于规则的逻辑,将提交的值得名称与控制器中方法的参数名称相匹配。DefaultModelBinder类知道如何处理简单和复杂的类型,因此,默认模型绑定器在大部分时间都能正常运行。

绑定简单类型

1
2
3
4
5
6
7
public class HomeController : Controller
{
public ActionResult Test(int num)
{
return View();
}
}

通过http://localhost:14303/Home/test?num=1就可以将1传入到参数num上

处理可选值

由于传递无效值而抛出的异常不会在控制器级别被检测出来,这意味着你不可能使用trycatch捕获他。

如果默认模型绑定器不能找到与参数相匹配的值,那么将null放入参数字典。

一般会与值类型一般通过使用可空类型或者赋默认值的方法避免抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class HomeController : Controller
{
public ActionResult Test(int? num)
{
return View();
}
}

public class HomeController : Controller
{
public ActionResult Test(int num=1)
{
return View();
}
}

默认模型绑定器可以映射所有的简单类型,如string,int,double,decimal,boolean,datetime,集合。在url中便是boolean需要借助true false字符串。

值提供程序和优先级

默认模型绑定器会使用所有已注册的值所提供程序将提交值于方法参数进行匹配。

下表既列出了获取值得途径同时也是获取值得优先级

优先级 集合 描述
1 Form 表单即报文头提交的值
2 RouteData Url路由中的值
3 QueryString Url中提交的值
4 Files 如果有的话,为上传文件的值

例子:假设你的路由是下面这个

1
2
3
4
5
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/test/{number}",
defaults: new { controller = "Home", action = "Index", number = 5 }
);

提交的url:/Home/Index/test/10?number=1

实际获得number值是10,因为routedata的优先级比querystring的高

绑定复杂类型

可以在方法签名上列出的参数没有数目的上限。但是,一个容器类往往比一个各种参数的长列表好。

可以通过强类型页面的html帮助器,也可以直接通过name提交的数据都会被绑定到容器类属性中

,不区分大小写

1
2
3
4
5
//容器类
public class Test
{
public string Name{get;set;}
}

url:Home/Index?name=luox78 ,照样绑定到 public ActionResult Index(Test t)

绑定集合

controller

1
2
3
4
5
6
7
8
9
10
11
12
public ActionResult Test(IList<string> email)
{
return View(new List<string>()
{
"1111@qq.com",
"1111@qq.com",
"1111@qq.com",
"1111@qq.com",
"1111@qq.com",
"1111@qq.com"
});
}

cshtml

1
2
3
4
5
6
7
8
9
@using (Html.BeginForm())
{
foreach (var item in Model)
{
<input type="text" name="email" value="@item"/>
<br>
}
<input type="submit" value="submit"/>
}

绑定带复杂类型的集合

复杂类型

1
2
3
4
5
6
7
8
9
10
public class Person
{
public string Name { get; set; }
public Test Test { get; set; }
}

public class Test
{
public string Message { get; set; }
}

controller

1
2
3
4
5
6
7
8
9
10
11
public ActionResult Test2(IList<Person> persons)
{
return View(new List<Person>()
{
new Person() {Name = "luox78",Test = new Test() {Message = "hello"}},
new Person() {Name = "luox78",Test = new Test() {Message = "hello"}},
new Person() {Name = "luox78",Test = new Test() {Message = "hello"}},
new Person() {Name = "luox78",Test = new Test() {Message = "hello"}},
new Person() {Name = "luox78",Test = new Test() {Message = "hello"}}
});
}

要想模型绑定成功,需要使用的是Prefix[index]给name,prefix是控制器方法签名形参名称

cshtml

1
2
3
4
5
6
7
8
9
10
11
12
13
@model IList<_02.Controllers.Person>

@using (Html.BeginForm())
{
foreach (var item in Model)
{
<input type="text" name="persons[@index].Name" value="@item.Name" />
<input type="text" name="persons[@index].Test.Message" value="@item.Test.Message" />
index++;
<br />
}
<input type="submit" value="submit" />
}

可以使用自定义模型绑定器来实现复杂类型的绑定

绑定上传的文件内容

上传文件参数类型为HttpPostedFileBase

1
2
3
4
5
6
7
8
9
10
public ActionResult Add(AddViewModel model)
{
if(model.file != null)
{
var file = model.file;
var fileName = Path.Combine(Server.MapPath("/"), Path.GetFileName(file.FileName));
file.SaveAs(fileName);
}
return View();
}

默认情况下,Asp.net请求不超过4M,包含所有的上传,报头,正文,在web.config里面可以添加httpRuntime的maxRequestLength配置上限

1
2
3
<system.web>
<httpRuntime targetFramework="4.5.2" maxRequestLength="6000"/>
</system.web>

上传多个文件 属性IList<HttpPostedFileBase> Picts{get;set;}

1
2
<input type="file" id="picts[0]" name="picts[0]" />
<input type="file" id="picts[1]" name="picts[1]" />

默认绑定器可以自定义的部分

Bind特性

属性 描述
Prefix 字符串属性,它表示用户绑定器解析的必须在提交值名称中发现的前缀。默认值为空字符串
Exclude 获取或设置不允许绑定的属性名称列表,用逗号隔开
Include 获取或设置允许绑定的属性名称列表,用逗号隔开

创建属性白名单

使用模型绑定器会存在潜在风险,就是默认模型绑定器会将所有的公共属性都填入复杂类型中去。

Person class

1
2
3
4
5
public class Person
{
public string Name { get; set; }
public string ForTest { get; set; }
}

只接受ForTest提交值

1
2
3
4
public ActionResult Test3([Bind(Include = "ForTest")]Person person)
{
return View();
}

创建属性黑名单

只排出ForTest

1
2
3
4
public ActionResult Test3([Bind(Exclude = "ForTest")]Person person)
{
return View();
}

加前缀的参数别名

模型绑定器会强制让方法参数名与提交的参数一直,Prefix可以改变这一点

1
2
3
4
public ActionResult Test([Bind(Prefix="email")]IList<string> emails)
{
...
}

高级模型绑定

创建自己的模型绑定器可以直接实现IModelBinder接口或者继承自DefaultModelBinder

如果继承自DefaultModelBinder,一般只要重写CreateModel实现自己的模型创建

注册自定义绑定器

1
2
3
4
5
6
7
8
9
10
11
void Application_Start()
{
...
ModelBinders.Binders[typeof(MyBinder)]=new MyBinder();
}

//使用
public ActionResult Index([ModelBinder(typeof(MyBinder))] Model model)
{
....
}

通过ModelBinder.Binders.DefaultBinder = new MyModelBinder();重写默认绑定器