现在的位置: 首页 > 综合 > 正文

MVC学习笔记六:模型绑定【上】

2017年10月09日 ⁄ 综合 ⁄ 共 10476字 ⁄ 字号 评论关闭

模型绑定

这一章主要记录一下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的值,点击提交按钮,同样是可以实现绑定的!


抱歉!评论已关闭.