ASP.NET MVC模型绑定1
一、模型绑定原理
模型绑定是指为Controller的Action方法的参数提供值的过程,例如我有一个名为Blog的实体类(准确的说是ViewModel),它有一个名为Title的属性,如果我在VIEW里定义一个文本框<input type="text" id="Title" name="Title"> 和一个提交按钮,然后我在Controller中定义一个Add(Blog blog)的方法,当我们在View中点击提交按钮,会惊奇的发现Add方法中的blog参数的Title值就是我们在文本框中输入的值。
View中用Blog类的强类型辅助方法:@Html.EditorFor(),将Blog类的各个属性名和生成的Html客户端控件ID绑定了起来。
当 MVC 收到一个 HTTP 请求,它将其路由到一个 Controller 特定的一个 Action 方法。它基于路由数据来决定运行哪个 Action 方法,然后将值从 HTTP 请求绑定到 Action 方法的参数中。例如URL:
http://contoso.com/movies/edit/2
因为路由模板看起来像这样{controller=Home}/{action=Index}/{id?}, movies/edit/2 路由到Movies Controller ,和它的 Edit Action 方法。同时接受到一个可选参数 id 。 Action 方法代码应该看起来像这样:
public IActionResult Edit(int? id)
MVC 尝试通过参数名将请求数据绑定到 Action 的参数上。 MVC 将使用参数名以及它的公开可设置的属性名称查询所有的值。在上面的例子中,只有一个参数命名为 id ,MVC 将路由值中名称相同的值绑定过去。除了路由值之外, MVC 会以一种固定的顺序从 HTTP 请求中的其他部分绑定数据。下面是模型绑定的数据源列表的绑定顺序:
- form表单中的数据 :这是通过 HTTP POST 请求发送的表单数据(包括 jQuery POST 请求)。
- RouteData中的数据 :由 routing 提供的路由数据集。
- QueryString中的数据:URI 的查询字符串的一部分。
提示:表单值,路由数据,以及查询字符串都以键值对的形式存储。
因为模型绑定要找一个命名为 id 的键,但是在表单值里没有命名为 id 的键,所以接下来将在路由数据中找寻这个键。在我们的例子中,它是匹配的。绑定发生时,该值转换为 integer 类型的 2。相同的请求使用 Edit(string id) 将转换成 string 类型的值 “2” 。
到目前为止的例子使用的都是简单类型。在 MVC 中简单类型是任何 .NET 原始类型或者带字符串的类型的转换器。如果 Action 方法的参数是一个类,这个类包含简单类型和复杂类型的属性,MVC 的模型绑定仍然可以很好的处理它。它使用反射和递归遍历复杂类型寻找匹配的属性。模型绑定寻找 parameter_name.parameter_name 的模式去绑定值到属性上。如果没有从表单中找到匹配的值,将尝试只通过 property_name 进行绑定。对于那些集合(Collection)类型,模型绑定会去匹配 parameter_name[index] 或者只是 [index] 。模型绑定对待字典(Dictionary)类型也是一样,寻找 parameter_name[key] 或只是 [key] ,前提是 Key是简单类型。 Key支持匹配HTML 和 Tag Helpers 为相同的模型类型生成的字段名。当创建或者编辑的绑定数据未通过验证的时候,回传值使得用户输入的表单字段仍然保留,方便了用户(不必重新输入全部数据)。
为了绑定发生,这个类必须有一个公开的默认构造函数,并且被绑定的成员必须是公开的,并且可写的属性。当模型绑定发生的时候只会通过默认的构造函数去实例化类型,然后设置属性的值。
当一个参数被绑定,模型绑定停止继续查找这个参数名并开始绑定下一个参数。如果绑定失败, MVC 不会抛出异常。你可以查询模型状态异常通过检查 ModelState.IsValid 属性。
提示:Controller 的 ModelState 属性中的每个 Entry 都是一个包含了 Errors 属性 的 ModelStateEntry。 你基本不需要去查询这个集合。使用 ModelState.IsValid 来替代它。
此外,还有一些特殊的数据类型在 MVC 执行模型绑定的时候需要考虑:
- IFormFile, IEnumerable<IFormFile>: 一个或多个通过 HTTP 请求上传的文件。
- CancelationToken:用于在异步 Controller 中取消活动。
这些类型可以被绑定到 Action 参数或者一个类的属性中。
一旦模型绑定完成,就会进行 验证 。默认的模型绑定适合绝大多数开发场景。它也是可扩展的,所以如果你有独特的需求,你可以自定义内置的行为。
二、通过 Attributes 自定义模型绑定行为
MVC 包含几种让你可以指定与默认绑定源不同行为的 Attribute 。比如,你可以通过使用 [BindRequired] 或者 [BindNever] Attribute 指定一个属性是否需要绑定,或者它是否应该不发生。另外你可以替换默认的数据源,指定模型绑定器(Model Binder)的数据源。下面的是模型绑定 Attribute 的列表:
- [BindRequired]:这个 Attribute 表示如果这个绑定不能发生,将添加一个模型状态错误(Model State Error)。
- [BindNever]:告诉模型绑定器(Model Binder)这个参数不进行绑定。
- [FromHeader], [FromQuery], [FromRoute], [FromForm]:通过这些来指定期望的绑定源。
- [FromServices]:这个Attribute 使用dependency injection 通过服务来绑定参数。
- [FromBody]:使用配置好的格式化器来从HTTP请求Body中绑定数据。格式化器的选择基于HTTP请求的 Content-Type
- [ModelBinder]:用来替换默认的模型绑定器(Model Binder),绑定源和名字。
当你需要替换模型绑定的默认行为时,Attribute 是非常有用的工具。
三、从 Http Request 的 body 中绑定格式化数据
HTTP请求数据能够支持各种各样的格式,包括 JSON 、XML以及许多其它的格式。当你使用 [FromBody] 特性的时候表示你想要从HTTP请求的Body中绑定参数, MVC使用一个格式化器的配置集来处理与HTTP请求的Content-Type 对应的请求数据。默认情况下MVC 包含一个 JsonInputFormatter 类用来处理 JSON 数据,但是你可以添加额外的格式化器来处理XML或者其它自定义格式。
提示:
JsonInputFormatter 是默认的格式化器,它是基于Json.NET。
ASP.NET 选择输入格式化器基于 Content-Type Header 以及参数的类型,除非这里有一个 Attribute 去指定其它的。如果你更喜欢使用 XML 或者其他格式,你必须在 Startup.cs 文件中进行配置,但是首先你必须使用 NuGet 引用 Microsoft.AspNetCore.Mvc.Formatters.Xml 。你的启动代码看起来应该像这样:
复制代码
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddXmlSerializerFormatters();
}
Startup.cs 文件中的代码包含了一个带有 services 参数的 ConfigureServices 方法,你可以使用它来为你的 ASP.NET 应用构建服务。在示例中,我们添加一个 XML 格式化器作为一个在此应用中 MVC 能够提供的的服务。 options 参数传入 AddMvc 方法允许你去添加和管理过滤器(Filter),格式化器(Formatter),以及其它 MVC 的系统选项从应用中启动。然后应用 各种各样的 Attribute 到 Controller 类或者 Action 方法上去实现你预期的效果。
四、绑定的例子
在WebForm,获取提交表单的值一般都是Request.Form["Title"]这样的方式
控制器方法的参数可以是字符串,整型变量,实体或者是List<实体>的方式获取表单提交的参数。
参数名称等内容需要与表单中的Html控件的name属性 一 一 对应的。
例如方法:
public ActionResult PersonAdd(int Id)
{
return View();
}
例如以上代码,它能够匹配Url中的Id参数。如以下两种方法Id都能够匹配到1
http://localhost/Home/PersonAdd/1
http://localhost/Home/PersonAdd?Id=1
再例如:
public ActionResult PersonAdd(string Name)
{
return View();
}
它能够匹配到表单中提交的张三: <input type="text" name="Name" value="张三" />
也能够匹配到Get请求的路径参数:http://localhost/Home/PersonAdd?Name=张三
如果是用实体,则会检查该实体的属性名与表单中name属性中对应的标签的值。
例如有如下实体:
public class Person_Model
{
public int Id { get; set; } public string Name { get; set; }
}
在Controller中的参数填写如下:
[HttpPost]
public ActionResult PersonAdd(Person_Model model)
{
if (ModelState.IsValid) //此处仅作演示,不考虑安全性
{
//插入数据库省略
return Redirect("/Home/PersonManager");
}
return View();
}
这样的话,模型绑定器会自动检查该实体的属性与Name一一对应的标签并绑定。如下表单的值将被绑定到model实体的属性中。
<input type="hidden" name="Id" value="1" />
<input type="text" name="Name" value="张三" />
简单参数和复杂参数
如果Action方法的参数类型是值类型和字符串类型,那么DefaultModelBinder将寻找与Action参数名称匹配的参数,如果没有对应的参数,那么Action的参数将试图赋予空引用。因此,对于简单类型的参数来说,参数的类型应该是可空的。
多数情况下,我们会通过一个Model对象来处理复杂的参数,DefaultModelBinder会遍历Model对象的属性来绑定参数。
如果不希望DefaultModelBinder对某个参数进行绑定,可以通过BindAttribute进行说明,其中定义了三个属性:
- Include表示需要绑定的属性,各个属性之间以逗号进行分隔。
- Exclude表示不需要绑定的属性,各个属性之前以逗号分隔。
- Prefix表示请求参数的前缀。 ??
这些标签可以定义在Model上,说明在参数绑定过程中需要绑定的属性或者不需要绑定的属性,如:
[Bind(Include = "Name,Birthday")]
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Birthday{ get; set; }
}
在UpdateModel方法中,指定包含的属性和不包含的属性。
UpdateModel(
person, //Model 对象
"person", //Prefix ??
new[] { "Id","Name" }, //Include
new [] { "Birthday" } //Exclude
);
五、模型显式绑定
UpdateModel与TryUpdateModel都用于显示模型绑定。如果绑定期间出现错误或者模型是无效的。
UpdateModel将抛出一个异常。因此UpdateModel要用try catch语句块包起来,而TryUpdateModel不会抛出异常,而是返回一个布尔类型的值,true表示绑定成功,false表示绑定失败。如:
[HttpPost]
public ActionResult PersonAdd() 方法无参数
{
Person_Model model = new Person_Model(); //定义类对象
try
{
UpdateModel(model); //显式绑定
//插入数据库
return Redirect("/Home/PersonManager");
}
catch
{
return View(model);
}
}
六、模型绑定的安全性【绑定部分属性、多类同名】
假设有如下实体:
public class Comment
{
public int Id { get; set; }
//评论者姓名
public string Name { get; set; }
//评论内容
public string Content { get; set; }
//是否已审核
public bool Approved { get; set; }
}
控制器中的方法:
public ActionResult CommentAdd(Comment com)
{
if (ModelState.IsValid)
{
//添加数据库
return Redirect("/Home/CommentManager");
}
else
{
return View(com);
}
}
在以上代码中,如果有恶意用户在表单数据中添加"Approved=true"来干预表单的提交,那么该评论将是默认就通过审核的。这时候我们可以使用Bind特性来防御重复提交攻击。
可以用下面的属性禁止某些属性的绑定:
[Bind(Include="Name,Content")] //白名单,只绑定这两个属性
[Bind(Exclude="Id,Approved")] //黑名单,不绑定这两个属性
public ActionResult CommentAdd([Bind(Exclude="Approved")]Comment com) 禁止Approved绑定
{
if (ModelState.IsValid)
{
//添加数据库
return Redirect("/Home/CommentManager");
}
else
{
return View(com);
}
}
另外,UpdateModel与TryUpdateModel也有一个重载版本来接收一个绑定列表:
UpdateModel(com, "", new string[] { "Id", "Name", "Content" }); 还可以另外定义一个ViewModel,仅仅包括需要绑定的属性。 另外,如果两个类有相同的Name属性,要同时绑定,区分HTML可以这样写:【多个类属性名称相同的情况】
<p>客户名称: <input type="text" name="customer.Name" style="width: 300px" /></p>
<p>销售员名称: <input type="text" name="salesman.Name" style="width: 300px" /></p>
ASP.NET MVC模型绑定1的更多相关文章
- ASP.NET MVC模型绑定的6个建议(转载)
ASP.NET MVC模型绑定的6个建议 发表于2011-08-03 10:25| 来源博客园| 31 条评论| 作者冠军 validationasp.netmvc.netasp 摘要:ASP.NET ...
- ASP.NET没有魔法——ASP.NET MVC 模型绑定
在My Blog中已经有了文章管理功能,可以发布和修改文章,但是对于文章内容来说,这里缺少最重要的排版功能,如果没有排版的博客很大程度上是无法阅读的,由于文章是通过浏览器查看的,所以文章的排版其实与网 ...
- ASP.NET没有魔法——ASP.NET MVC 模型绑定解析(下篇)
上一篇<ASP.NET没有魔法——ASP.NET MVC 模型绑定解析(上篇)>文章介绍了ASP.NET MVC模型绑定的相关组件和概念,本章将介绍Controller在执行时是如何通过这 ...
- asp.net Mvc 模型绑定项目过多会导致页面运行时间卡
asp.net Mvc 模型绑定项目过多会导致页面运行时间卡的问题. 解决方式就是采用ModelView方式进行精简,已减少模型绑定及验证的时间.
- [转] ASP.NET MVC 模型绑定的功能和问题
摘要:本文将与你深入探究 ASP.NET MVC 模型绑定子系统的核心部分,展示模型绑定框架的每一层并提供扩展模型绑定逻辑以满足应用程序需求的各种方法. 同时,你还会看到一些经常被忽视的模型绑定技术, ...
- ASP.NET MVC——模型绑定
这篇文章我们来讲讲模型绑定(Model Binding),其实在初步了解ASP.NET MVC之后,大家可能都会产生一个疑问,为什么URL片段最后会转换为例如int型或者其他类型的参数呢?这里就不得不 ...
- ASP.NET没有魔法——ASP.NET MVC 模型绑定解析(上篇)
前面文章介绍了ASP.NET MVC中的模型绑定和验证功能,本着ASP.NET MVC没有魔法的精神,本章内容将从代码的角度对ASP.NET MVC如何完成模型的绑定和验证进行分析,已了解其原理. 本 ...
- ASP.NET MVC 模型绑定
模型绑定指的是MVC从浏览器发送的HTTP请求中为我们创建.NET对象,在HTTP请求和C#间起着桥梁的作用.模型绑定的一个最简单的例子是带参数的控制器action方法,比如我们注册这样的路径映射: ...
- 禁止ASP.NET MVC模型绑定时将空字符串绑定为null
为model添加[DisplayFormat(ConvertEmptyStringToNull = false)] [Display(ResourceType = typeof(AppStrings) ...
随机推荐
- 使用Arduino Nano驱动Lora模块
使用Arduino Nano驱动Lora模块 为什么选用Lora 射频通信芯片有很多种,但是一般在同样功耗下,距离没有Lora远;同等范围下,没有Lora节能. Lora通信只适用于低速率,高延时的场 ...
- day 12 函数
函数 函数的定义和调用 def 函数名(形参): 函数体 return 返回值 调用 函数名(实参) 站在形参的角度上: 位置参数, *args, 默认参数(陷阱), 关键字参数, **kwargs ...
- pycharm out of memory 闪退
不知道从什么时候开始,python开始报 out of memory. 把pycharm64.exe.vmoptions -Xmx 调成1024m或者2048m pycharm就打不开了 低了不能用, ...
- Verilog-同步FIFO
参考博客:https://blog.csdn.net/hengzo/article/details/49683707 1.基本框图 1)双端口RAM加两个读写指针 2)写数据.写使能.写满:读数据.读 ...
- Wannafly Winter Camp 2020 Day 6J K重排列 - dp
求 \(K\) 是多少个 \(n\) 元置换的周期.\(T\leq 100, n\leq 50, K \leq 10^{18}\) Solution 置换可以被试做若干个环组成的有向图,于是考虑 dp ...
- A - Kvass and the Fair Nut 二分
The Fair Nut likes kvass very much. On his birthday parents presented him nn kegs of kvass. There ar ...
- 【Spring】利用spring的JdbcTemplate查询返回结果映射到自定义类型
// org.springframework.jdbc.core.JdbcTemplate 中的查询方法基本都有支持参数RowMapper<T> rowMapper的重载方法.下面只是随便 ...
- 曼孚科技:AI算法领域常用的39个术语(上)
算法是人工智能(AI)核心领域之一. 本文整理了算法领域常用的39个术语,希望可以帮助大家更好地理解这门学科. 1. Attention 机制 Attention的本质是从关注全部到关注重点.将有限 ...
- Java中的实体类--Serializable接口、transient 关键字
在java中,实体类是一个非常重要的概念,我们可以在实体类中封装对象.设置其属性和方法等.关于实体类,也经常涉及到适配器模式.装饰者模式等设计模式.那么在实际代码开发中,关于实体类的注意事项有哪些呢? ...
- 问题 B: 基础排序III:归并排序
#include <cstdio> #include <vector> #include <algorithm> using namespace std; void ...