模型绑定
这一章主要记录一下MVC模型绑定
一.认识模型绑定
官方详细介绍模型绑定的资料我没找到,只是在MSDN上讲DefaultModelBinder 类时介绍了一下:将浏览器请求映射到数据对象。
这句话刚看上去不大明白意思,还是用自己的话总结一下:
模型绑定实际上是:
服务器端代码利用用户在表单中输入的数据(或其它HTTP请求携带的数据),来构造动作方法所需要的参数对象的过程。数据的流向是从客户端的HTML表单到服务器端动作方法。
更进一层的解释是:
当我们在浏览器输入一个地址即访问一个动作时,动作调用器会负责在调用方法之前获取该方法所需的所有参数。而
默认动作调用器依赖于模型绑定来获取动作方法中的参数值
。其中每一个参数依赖于各自的模型绑定器,它们可能是用户自定义的模型绑定器,也可能是默认的模型绑定器。
下面从一个简单的示例开始演示一下默认模型绑定器。
二.使用默认模型绑定
1)绑定简单类型
打开VS,新建一个空模板的MVC 3项目如下:
在Models文件夹下面新建一个Sheep类文件,如下:
namespace MvcModelBind.Models
{
public class Sheep
{
private string _name = "No name!";
public string Name
{
get { return _name; }
set { _name = value; }
}
}
}
在Controllers文件夹下添加一个控制器,如下:
在该控制器下新建一个方法,注意添加Sheep的引用:
public ActionResult ModelBindSheep(Sheep sheep)
{
return View(sheep);
}
F6编译项目,为上面的方法新建一个强类型视图,如下:
将视图代码修改为:
@model MvcModelBind.Models.Sheep
@{
ViewBag.Title = "ModelBindSheep";
}
<h2>ModelBinding示例:绑定Sheep对象的Name属性</h2>
@using (Html.BeginForm("ModelBindSheep", "Sheep"))
{
@:小羊的名字:@Html.TextBoxFor(n => n.Name, new { style = "background-color:#FF83FA" })<br />
@:目前小羊的名字是:@Model.Name <br />
<input type="submit" name="btn" value="提交" />
}
运行查看网页:注意将网址改成对应的:/控制器名/action名:
更改名字,点击提交按钮会看到名字随之变化,这里断点进action方法里看一下,具体的方法参数值:
实际上,上面的模型绑定流程大体上可以分为如下:
1)当我们点击提交按钮时,模型绑定器发现页面需要绑定一个Sheep类,而且这个类有一个string型的属性Name需要赋值;
2)于是模型绑定器就要找能为这个Name属性赋值的数据源。它首先在表单中查找,结果发现TextBox的name值是“Name”,于是就认为这个控件的值可以为Sheep类 的Name属性赋值;
3)于是模型绑定器就将textBox的value值取出来,转化成能为Sheep类的Name属性赋值的类型(这里转化为string),并将转化后的值赋给Sheep类的Name属性;
4)赋值完成后,模型绑定器就能构造出一个Sheep类的实例对象,该对象里的属性值都是刚刚从表单中取出绑定过来的。然后将这个实例对象送给动作调用器;
5)动作调用器在调用action方法之前,将收到的实例对象注入到动作方法的参数里去。对应上面的示例,就是注入到参数sheep中。
经过上述几部,才有我们在断点时监视到的参数sheep的Name属性值随TextBox值变化而变化的情形。
如果要说什么是默认模型绑定,那么上面的便是咯
注:上面演示的示例,模型绑定器是在表单中找到与参数名(Name)匹配的数据源的;实际上默认的模型绑定器不仅仅会从表单中查找,它查找数据源的顺序是:
1.用户在HTML表单form中提供的值;(即Request.Form)
2.从应用程序路由获得的值;(即RouteData.Values)
3.URL中查询字符串部分的值;(即Request.QueryString)
4.请求中的上传文件。(即Request.Files)
以上,默认绑定器只要找到一个值,搜索便会停止。
2)绑定复合类型
a)绑定复合类型简单示例
上面绑定的仅仅是一个string型的属性Name,接下来绑定一个复杂类型AdditionalInformation AddInfo
其中AdditionalInformation 定义如下:
namespace MvcModelBind.Models
{
public class AdditionalInformation
{
public string Country { get; set; }
public string City { get; set; }
}
}
在Sheep类中添加一个新的复合属性AddInfo:
public class Sheep
{
private string _name = "No name!";
public string Name
{
get { return _name; }
set { _name = value; }
}
public AdditionalInformation AddInfo { get; set; }
}
更改ModelBindSheep方法对应的视图代码为:
@model MvcModelBind.Models.Sheep
@{
ViewBag.Title = "ModelBindSheep";
}
<h2>ModelBinding示例:绑定Sheep对象的Name属性</h2>
@using (Html.BeginForm("ModelBindSheep", "Sheep"))
{
@:小羊的名字:@Html.TextBoxFor(n => n.Name, new { style = "background-color:#FF83FA" })<br />
@:目前小羊的名字是:@Model.Name <br />
@:小羊所在国家: @Html.EditorFor(c=>c.AddInfo.Country) <br />
@:小羊所在的城市: @Html.EditorFor(c=>c.AddInfo.City) <br />
<input type="submit" name="btn" value="提交" />
}
运行查看网页,对应的国家及城市Html源码:
小羊所在国家: <input class="text-box single-line" id="AddInfo_Country" name="AddInfo.Country" type="text" value="" /> <br />
小羊所在的城市: <input class="text-box single-line" id="AddInfo_City" name="AddInfo.City" type="text" value="" /> <br />
可以看到name属性是模型Sheep类的AddInfo属性名+对应的AdditionalInformation类的Country/City属性名组合而成。
实际上可以完全手动写上面的HTML代码,而不必借用Html辅助器,只不过用支持Lambda表达式的Html辅助器生成网页源码比较方便而已。
b)指定自定义前缀
前面所有的示例都有一个共同的特点就是:视图中仅仅绑定了一个模型类,即
一个Sheep类型的model
@model MvcModelBind.Models.Sheep
我在视图代码中增加另一个Sheep实例:
@using MvcModelBind.Models;
@model MvcModelBind.Models.Sheep
@{
Sheep anotherSheep = new Sheep
{
Name = "Tom",
AddInfo = new AdditionalInformation { Country = "America", City = "sanfrancisco" }
};
}
@{
ViewBag.Title = "ModelBindSheep";
}
<h2>ModelBinding示例:绑定Sheep对象的Name属性</h2>
@using (Html.BeginForm("ModelBindSheep", "Sheep"))
{
@:这是当前小羊的名字: @Html.EditorFor(a=>a.Name) <br />
@:这是当前小羊的国家: @Html.EditorFor(a=>a.AddInfo.Country) <br />
@:这是当前小羊的城市: @Html.EditorFor(a=>a.AddInfo.City)
<p >--------------------------------------------------------</p>
@:这是额外小羊的名字: @Html.EditorFor(a=>anotherSheep.Name) <br />
@:这是额外小羊的国家: @Html.EditorFor(a=>anotherSheep.AddInfo.Country) <br />
@:这是额外小羊的城市: @Html.EditorFor(a=>anotherSheep.AddInfo.City) <br />
<input type="submit" name="btn" value="提交" />
}
编译运行,无论我如何修改虚线下面的三个TextBox的值,点击提交按钮后这三个TextBox始终是显示:
这说明虚线下面的三个根本就没参与模型绑定。在对应的动作方法中添加一个参数:
public ActionResult ModelBindSheep(Sheep sheep,Sheep sheep1)
{
return View(sheep);
}
编译运行,还是和上面一样,无
论我如何修改虚线下面的三个TextBox的值,点击提交按钮后这三个TextBox始终是恢复上图的样子。
其实模型绑定器在查找sheep1对应的参数值时,会去查找name为sheep1.Name/AddInfo.Country/AddInfo.City的数据源,发现压根就找不到,所以点击提交按钮后,虚线下面的三个TextBox又恢复原值。
看一下页面这三个TextBox对应的name:
<p >--------------------------------------------------------</p>
这是额外小羊的名字: <input class="text-box single-line" id="anotherSheep_Name" name="anotherSheep.Name" type="text" value="Tom" /> <br />
这是额外小羊的国家: <input class="text-box single-line" id="anotherSheep_AddInfo_Country" name="anotherSheep.AddInfo.Country" type="text" value="America" /> <br />
这是额外小羊的城市: <input class="text-box single-line" id="anotherSheep_AddInfo_City" name="anotherSheep.AddInfo.City" type="text" value="sanfrancisco" /> <br />
可见,这些name都有一个前缀:anotherSheep,这是我在视图中定义的额外Sheep类的实例名。
这样一来,我就可以将动作方法中第二个参数名改成
anotherSheep,来实现正确的模型绑定:
public ActionResult ModelBindSheep(Sheep sheep, Sheep anotherSheep)
{
return View(sheep);
}
这时候,编译运行,修改这三个TextBox值,提交页面,会发现值都会随之变化了;
其实除了将方法的第二个参数名改成anotherSheep外,还有另一种方法,见下:
public ActionResult ModelBindSheep(Sheep sheep,[Bind(Prefix="anotherSheep")] Sheep sheep1)
{
return View(sheep);
}
该方式是利用Bind注解属性来告诉模型绑定器找数据源时,应该找数据源name以什么来开头的。
c)绑定或不绑定某些属性
假如我不想绑定Sheep中的Name属性,只想绑定AddInfo属性,但是我前台页面又有对应的Name数据源,因为绑定器会自动找到并绑定它。
可以利用Bind注解属性:
public ActionResult ModelBindSheep([Bind(Include="AddInfo")] Sheep sheep)
{
return View(sheep);
}
或:
public ActionResult ModelBindSheep([Bind(Exclude="Name")] Sheep sheep)
{
return View(sheep);
}
或者在类定义上使用:
[Bind(Exclude = "Name")]
public class Sheep
{
private string _name = "No name!";
public string Name
{
get { return _name; }
set { _name = value; }
}
public AdditionalInformation AddInfo { get; set; }
}
或:
[Bind(Include = "AddInfo")]
public class Sheep
{
private string _name = "No name!";
public string Name
{
get { return _name; }
set { _name = value; }
}
public AdditionalInformation AddInfo { get; set; }
}
但是一个需要注意的是:如果在类定义上使用了:
[Bind(Exclude = "Name")]
又在动作方法参数上使用了:
[Bind(Include="Name")]
那么,这个Name属性仍然是不会被绑定的!
3)绑定到集合
上面的示例均是绑定的单独对象,即使使用额外对象也是屈指可数,实际应用中可能会要求绑定几十到几百个对象。
下面以一个简单的示例介绍绑定到集合,将原先的视图代码修改为:
@using MvcModelBind.Models;
@model List<MvcModelBind.Models.Sheep>
@{
ViewBag.Title = "ModelBindSheep";
int i = 0;
}
<h2>绑定到集合</h2>
@using (Html.BeginForm("ModelBindSheep", "Sheep"))
{
for ( i = 0; i < Model.Count; i++)
{
@:第 @(i+1) 只羊的名字: @Html.EditorFor(n=>n[i].Name) <br />
@:第 @(i+1) 只羊的国家: @Html.EditorFor(n=>n[i].AddInfo.Country) <br />
@:第 @(i+1) 只羊的城市: @Html.EditorFor(n=>n[i].AddInfo.City) <br />
<p>----------------------------------------------------</p>
}
<input type="submit" name="btn" value="提交" />
}
对应的方法修改为:
public ActionResult ModelBindSheep(List<Sheep> sheeps)
{
if (sheeps==null)
{
sheeps = new List<Sheep>()
{
new Sheep { Name="Tom" , AddInfo=new AdditionalInformation{Country="China",City="ShangHai"}},
new Sheep { Name="Jack" , AddInfo=new AdditionalInformation{Country="America",City="sanfrancisco"}},
new Sheep { Name="Jerry" , AddInfo=new AdditionalInformation{Country="France",City="London"}},
};
}
return View(sheeps);
}
编译运行:
对应表单中的源码:
<form action="/Sheep/ModelBindSheep" method="post">
第 1 只羊的名字: <input class="text-box single-line" name="[0].Name" type="text" value="Tom" />
<br />
第 1 只羊的国家: <input class="text-box single-line" name="[0].AddInfo.Country" type="text" value="China" />
<br />
第 1 只羊的城市: <input class="text-box single-line" name="[0].AddInfo.City" type="text" value="ShangHai" />
<br />
<p>----------------------------------------------------</p>
第 2 只羊的名字: <input class="text-box single-line" name="[1].Name" type="text" value="Jack" />
<br />
第 2 只羊的国家: <input class="text-box single-line" name="[1].AddInfo.Country" type="text" value="America" />
<br />
第 2 只羊的城市: <input class="text-box single-line" name="[1].AddInfo.City" type="text" value="sanfrancisco" />
<br />
<p>----------------------------------------------------</p>
第 3 只羊的名字: <input class="text-box single-line" name="[2].Name" type="text" value="Jerry" />
<br />
第 3 只羊的国家: <input class="text-box single-line" name="[2].AddInfo.Country" type="text" value="France" />
<br />
第 3 只羊的城市: <input class="text-box single-line" name="[2].AddInfo.City" type="text" value="London" />
<br />
<p>----------------------------------------------------</p>
<input type="submit" name="btn" value="提交" />
</form>
修改对应TextBox的值,提交表单后,可以看到值都随之变化,说明绑定成功!
4)绑定非数字索引的集合
这里借助于隐藏Input元素,作为指定数据的的键,只要将表单中的视图代码修改为:@using (Html.BeginForm("ModelBindSheep", "Sheep"))
{
<input type="hidden" name="index" value="firstSheep" />
@:第 @(i+1) 只羊的名字: @Html.Editor("[firstSheep].Name") <br />
@:第 @(i+1) 只羊的国家: @Html.Editor("[firstSheep].AddInfo.Country") <br />
@:第 @(++i) 只羊的城市: @Html.Editor("[firstSheep].AddInfo.City") <br />
<p>----------------------------------------------------</p>
<input type="hidden" name="index" value="secondSheep" />
@:第 @(i+1) 只羊的名字: @Html.Editor("[secondSheep].Name") <br />
@:第 @(i+1) 只羊的国家: @Html.Editor("[secondSheep].AddInfo.Country") <br />
@:第 @(++i) 只羊的城市: @Html.Editor("[secondSheep].AddInfo.City") <br />
<p>----------------------------------------------------</p>
<input type="hidden" name="index" value="thirdSheep" />
@:第 @(i+1) 只羊的名字: @Html.Editor("[thirdSheep].Name") <br />
@:第 @(i+1) 只羊的国家: @Html.Editor("[thirdSheep].AddInfo.Country") <br />
@:第 @(i+1) 只羊的城市: @Html.Editor("[thirdSheep].AddInfo.City") <br />
<input type="submit" name="btn" value="提交" />
}
即可;
5)绑定到字典
同样借助于隐藏input元素,只不过元素的name属性变成了字典的键,修改视图代码为如下:
@using MvcModelBind.Models;
@model Dictionary<string,MvcModelBind.Models.Sheep>
@{
ViewBag.Title = "ModelBindSheep";
int i = 0;
}
<h2>绑定到字典</h2>
@using (Html.BeginForm("ModelBindSheep", "Sheep"))
{
<input type="hidden" name="[0].key" value="firstSheep" />
@:第 @(i+1) 只羊的名字: @Html.Editor("[0].value.Name") <br />
@:第 @(i+1) 只羊的国家: @Html.Editor("[0].value.AddInfo.Country") <br />
@:第 @(++i) 只羊的城市: @Html.Editor("[0].value.AddInfo.City")
<p>----------------------------------------------------</p>
<input type="hidden" name="[1].key" value="secondSheep" />
@:第 @(i+1) 只羊的名字: @Html.Editor("[1].value.Name") <br />
@:第 @(i+1) 只羊的国家: @Html.Editor("[1].value.AddInfo.Country") <br />
@:第 @(++i) 只羊的城市: @Html.Editor("[1].value.AddInfo.City")
<p>----------------------------------------------------</p>
<input type="hidden" name="[2].key" value="thirdSheep" />
@:第 @(i+1) 只羊的名字: @Html.Editor("[2].value.Name") <br />
@:第 @(i+1) 只羊的国家: @Html.Editor("[2].value.AddInfo.Country") <br />
@:第 @(++i) 只羊的城市: @Html.Editor("[2].value.AddInfo.City") <br />
<input type="submit" name="btn" value="提交" />
}
对应的方法修改为:
public ActionResult ModelBindSheep(Dictionary<string,Sheep> sheeps)
{
if (sheeps == null)
{
sheeps = new Dictionary<string, Sheep>();
sheeps.Add("firstSheep", new Sheep { Name="Tom" , AddInfo=new AdditionalInformation{Country="China",City="ShangHai"}});
sheeps.Add("secondSheep", new Sheep { Name = "Jack", AddInfo = new AdditionalInformation { Country = "America", City = "sanfrancisco" }});
sheeps.Add("thirdSheep", new Sheep { Name = "Jerry", AddInfo = new AdditionalInformation { Country = "France", City = "London" }});
}
return View(sheeps);
}
编译运行,修改TextBox的值,点击提交按钮,同样是可以实现绑定的!