• ASP.NET MVC学习三-数据传递之模型绑定


    一:简单的模型绑定

    在ASP.NET MVC中是模型绑定来解析客户端传过来的数据的,简单的来说就更近一步来封装了获得数据的手段,让用户更方便的来获取数据了

    我们来简单做一个例子

    public ActionResult Index()
            {
                return View();
            }
    
            [HttpPost]
            public ActionResult Index(string username)
            {
                ViewData["username"] = username;
                return View();
            }

    这里我们定义了一个Action,[httpPost]限定Action只能被post请求访问,我们在其参数中定义一个名字username的字符串

    view视图中的代码如下

    @{
        ViewBag.Title = "Index";
    }
    
    <form method="post">
        <h1>简单模型绑定</h1>
        姓名:<input type="text" name="username" id="username" />
        <input type="submit" value="提交" />
    </form>
    @if (ViewData["username"] != null)
    {
        <h1>@ViewData["username"]</h1>
    }

    这里注意模型绑定的name属性需要和view视图中的input标签工具name相同

    image

    二:使用FormCollection来进行模型绑定

    在Asp.net中我们习惯了用request.Form来获取提交的表单的值,当然在MVC中我们可以很方便的获取其值

    public ActionResult Index()
            {
                return View();
            }
    
            [HttpPost]
            public ActionResult Index(FormCollection e)
            {
                ViewData["username"] = e["username"];
                ViewData["Age"] = e["Age"];
                return View();
            }

    在视图中代码为

    <form method="post">
        <h1>简单模型绑定</h1>
        姓名:<input type="text" name="username" id="username" />
        年龄:<input type="text" name="Age" id="Age" /><br />
        <input type="submit" value="提交" />
    </form>
    @if (ViewData["username"] != null)
    {
        <label>姓名:</label><h1>@ViewData["username"]</h1>
        <label>年龄:</label><h1>@ViewData["Age"]</h1>
    }

    我们可以通过这种方法清晰的看到一样是能获取数据

    image

    三:模型绑定之传递对象

    之前我们讨论的都是通过表单传递一些简单的值,现在如果要传递一个model对象能不能呢?我们先在Model层添加一个People得吧

    public class People
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public int Age { get; set; }
        }

    在Controller将代码修改

    public ActionResult Index()
            {
                return View();
            }
    
            [HttpPost]
            public ActionResult Index(People people)
            {
                ViewData["Id"] = people.Id;
                ViewData["Name"] = people.Name;
                ViewData["Age"] = people.Age;
                return View();
            }

    此时我们将模型绑定为People类型

    View中视图修改为

    <form method="post">
        Id:<input type="text" name="Id" id="Id" />
        姓名:<input type="text" name="Name" id="Name" />
        年龄:<input type="text" name="Age" id="Age" /><br />
        <input type="submit" value="提交" />
    </form>
    @if (ViewData["Id"] != null)
    {
        <label>Id:</label><h1>@ViewData["Id"]</h1>
        <label>姓名:</label><h1>@ViewData["Name"]</h1>
        <label>年龄:</label><h1>@ViewData["Age"]</h1>
    }

    当然这里我们将定义的Input标签的name属性与类people名字一样

    image

    到这里你可能又会觉得那能不能传递多个对象呢?

    我们将Controller的代码改动一下

    public ActionResult Index()
            {
                return View();
            }
    
            [HttpPost]
            public ActionResult Index(People people1, People people2)
            {
                ViewData["Id1"] = people1.Id;
                ViewData["Name1"] = people1.Name;
                ViewData["Age1"] = people1.Age;
    
                ViewData["Id2"] = people2.Id;
                ViewData["Name2"] = people2.Name;
                ViewData["Age2"] = people2.Age;
                return View();
            }
    <form method="post">
        Id1:<input type="text" name="people1.Id" id="people1.Id" />
        姓名1:<input type="text" name="people1.Name" id="people1.Name" />
        年龄1:<input type="text" name="people1.Age" id="people1.Age" /><br />
         Id2:<input type="text" name="people2.Id" id="people2.Id" />
        姓名2:<input type="text" name="people2.Name" id="people2.Name" />
        年龄2:<input type="text" name="people2.Age" id="people2.Age" /><br />
        <input type="submit" value="提交" />
    </form>
    @if (ViewData["Id1"] != null)
    {
        <label>Id1:</label><h1>@ViewData["Id1"]</h1>
        <label>姓名1:</label><h1>@ViewData["Name1"]</h1>
        <label>年龄1:</label><h1>@ViewData["Age1"]</h1>
        
          <label>Id2:</label><h1>@ViewData["Id2"]</h1>
        <label>姓名2:</label><h1>@ViewData["Name2"]</h1>
        <label>年龄2:</label><h1>@ViewData["Age2"]</h1>
    }

    注意:View视图我们必须将标签的name属性设置和模型绑定名字一样

    Model Binding(模型绑定)是Http请求和Action方法之间的桥梁,它根据Action方法中的Model类型创建.NET对象,并将Http请求数据经过转换赋给该对象

    模型绑定原理

    以HomeController下Index的简单例子来查看

    public ActionResult Index(int id = 0) {
        viewdata["Id"]=id;
         return view();
    }

    Action方法是由默认的Action Invoke(即ControllerActionInvoker类)来调用,Action方法是由默认的Action Invoker(即ControllerActionInvker类)来调用,Action Invoker 依靠 Model Binder(模型绑定器) 来创建调用 Action 方法需要的数据对象。我们可以通过 Model Binder 实现的接口来了解它的功能,该接口是 IModelBinder

    namespace System.Web.Mvc { 
        public interface IModelBinder { 
            object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext); 
        } 
    }

    当 action invoker 需要调用一个 action 方法时,它先看这个 action 方法需要的参数,然后为每个参数找到和参数的类型对应的 Model Binder。对于我们以上示例,Action Invoker 会先检查action 方法,发现它有一个int的参数,然后它会定位到负责给其类型提供值的 Binder,并调用该 Binder 的 BindModel 方法。该方法再根据 Action 方法参数名称从路由信息中获取其类型的值,最后把该值提供给 Action Invoker。

    MVC 框架内置默认的 Model Binder 是 DefaultModelBinder

    当Action Invoker没有找到自定义的Binder时,则默认使用DefaultModelBinder,默认情况下DefaultModelBinder从如下4中途径找到绑定的值

    1.Request.Form,HTML form 元素提供的值。

    2.RouteData.Values,通过应用程序路由提供的值。

    3.Request.QueryString,所请求 URL 的 query string 值。

    4.Request.Files,客户端上传的文件。

    DefaultModelBinder会按照上面的顺序来查找对应的值

    1.Request.Form["id"]

    2.RouteData.Values["id"]

    3.Request.QueryString["id"]

    4.Request.Files["id"]

    复合类型的绑定

    复合类型:任何不能被 TypeConverter 类转换的类型(大多指自定义类型),否则称为简单类型,对于复合类型,DefaultModelBinder 类通过反射获取该类型的所有公开属性,然后依次进行绑定。

    依然看上面的例子

    public class People
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public int Age { get; set; }
        }

    在Action方法中

    public ActionResult CreatePerson(People model) { 
          return View(model);  
    }
    默认的 model binder 发现 action 方法需要一个 People对象的参数,会依次处理 People的每个属性。对于每个简单类型的属性,它和前面的例子一样去请求的数据中查找需要的值,例如Request.Form["Id"]去绑定Action参数model中的Id的值

    但是如果一个复合类型的属性也是复合类型

    public class People
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public int Age { get; set; }
            public Address HomeAddress { get; set; } 
        }
    
    public class Address { 
        public string City { get; set; } 
        public string Country { get; set; } 
    }

    表单提交后,model binder 会在 Request.Form["HomeAddress.Country"] 中查找到 People.HomeAddress 的 Country 属性的值。当Model binder 检查到 People类型参数的 HomeAddress 属性是一个复合类型,它会重复之前的查找工作,为 HomeAddress 的每个属性查找值,唯一不同的是,查找的时候用的名称不一样。

    应用 Bind 特性

    有时候我们还会遇到这样的情况,某个 action 方法的参数类型是某个对象的属性的类型,如下面这个 DisplayAddress action 方法:

    public ActionResult DisplayAddress(Address address) { 
        return View(address); 
    }
    @model MvcApplication1.Models.Person 
    ...
    @using(Html.BeginForm("DisplayAddress", "Home")) { 
        <div>@Html.LabelFor(m => m.PersonId)@Html.EditorFor(m=>m.PersonId)</div> 
        <div> 
            @Html.LabelFor(m => m.HomeAddress.City) 
            @Html.EditorFor(m=> m.HomeAddress.City)
        </div> 
        <div> 
            @Html.LabelFor(m => m.HomeAddress.Country) 
            @Html.EditorFor(m=> m.HomeAddress.Country)
        </div> 
        <button type="submit">Submit</button> 
    }

    那么我们如何把 Person 类型的对象传递给 DisplayAddress(Address address) 方法呢?点提交按钮后,Binder 能为 Address 类型的参数绑定 Person 对象中的 HomeAddress 属性值吗?

    经过验证视图中的值确实不能传递给DisplayAddress中的参数address,不能模型绑定的原因是问题在于生成 form 表单的 name 属性有 HomeAddress 前缀(name="HomeAddress.Country"),model binder 会通过request.form[“Country”]去进行绑定,因为提交上来的name有HoneAddress.Country有了HomeAddress所以无法绑定

    这时我们利用Bind绑定特性就可以解决问题

    public ActionResult DisplayAddress([Bind(Prefix="HomeAddress")]Address address) {
        return View(address);
    }

    注意:使用 Bind 特性指定了前缀后,需要提交的表单元素的 name 属性必须有该前缀才能被绑定。

    Bind 特性还有两个属性,Exclude 和 Include

    Exclude:在绑定时不会对 Address 这个 Model 的 Country 属性绑定值

    public ActionResult DisplayAddress([Bind(Prefix = "HomeAddress", Exclude = "Country")]Address address) {
        return View(address);
    }

    Include:Binder 在给 Address 模型绑定时只会给 Country 属性绑定值

    [Bind(Include = "Country")]
    public class Address {
        public string City { get; set; }
        public string Country { get; set; }
    }

    绑定到数组

    public ActionResult Names(string[] names) {
            names = names ?? new string[0];
            return View(names);
        }
    Names action方法有一个名为 names 的数组参数,Model Binder 将查找所有名称为 names 的条目的值,并创建一个 Array 对象存储它们。
    @model string[]
    @{
        ViewBag.Title = "Names";
    }
    
    <h2>Names</h2>
    @if (Model.Length == 0) {
        using (Html.BeginForm()) {
            for (int i = 0; i < 3; i++) {
                <div><label>@(i + 1):</label>@Html.TextBox("names")</div>
            }
            <button type="submit">Submit</button>
        }
    }
    else {
        foreach (string str in Model) {
            <p>@str</p>
        }
        @Html.ActionLink("Back", "Names");
    }

    重要:绑定到集合

    先创建一个带有 IList<Address> 参数的 action 方法:

    public ActionResult Address(IList<Address> addresses) {
        addresses = addresses ?? new List<Address>();
        return View(addresses);
    }

    在 View 中表单元素的 name 属性应该怎样命名才能被 Model Binder 识别为集合呢?

    @using MvcApplication1.Models
    @model IList<Address>
    @{
        ViewBag.Title = "Address";
    }
    
    <h2>Addresses</h2>
    @if (Model.Count() == 0) {
        using (Html.BeginForm()) {
            for (int i = 0; i < 2; i++) {
                <fieldset>
                    <legend>Address @(i + 1)</legend>
                    <div><label>City:</label>@Html.Editor("[" + i + "].City")</div>
                    <div><label>Country:</label>@Html.Editor("[" + i + "].Country")</div>
                </fieldset>
            }
            <button type="submit">Submit</button>
        }
    }
    else {
        foreach (Address str in Model) {
            <p>@str.City, @str.Country</p>
        }
        @Html.ActionLink("Back", "Address");
    }

    当 Model Binder 发现 Address action 方法需要一个 Address 集合作为参数时,它便从提交的数据中从索引 [0] 开始查找和 Address 的属性名称相同的数据值.

    手动调用model Bind方法

    使用UpdateModel来指定调用model Bind的方法来指定只从某一数据源来啊获取数据

    public ActionResult Address() {
        IList<Address> addresses = new List<Address>();
        UpdateModel(addresses, new FormValueProvider(ControllerContext));
        return View(addresses);
    }
    UpdateModel 方法指定了第二个参数是一个 FormValueProvider 的实例,它将使用 Model Binder 从只从 Request.Form 中查找需要的数据.FormValueProvider 类是 IValueProvider 接口的实现,是 Value Provider 中的一种,相应的,RouteData.Values、Request.QueryString 和 Request.Files 的 Value Provider 分别是 RouteDataValueProvider、QueryStringValueProvider和HttpFileCollectionValueProvider。

    自定义Value Provider

    通过自定义 Value Provider 我们可以为 Model Binding 添加自己的数据源。前面我们讲到了四种内置 Value Provider 实现的接口是 IValueProvider,我们可以实现这个接口来自定义一个 Value Provider。先来看这个接口的定义:

    namespace System.Web.Mvc { 
        public interface IValueProvider { 
            bool ContainsPrefix(string prefix); 
            ValueProviderResult GetValue(string key); 
        } 
    }

    ContainsPrefix 方法是 Model Binder 根据给定的前缀用来判断是否要解析所给数据。GetValue 方法根据数据的key返回所需要值。下面我们添加一个 Infrastructure 文件夹,创建一个名为 CountryValueProvider 的类来实现这个接口,代码如下:

    public class CountryValueProvider : IValueProvider {
        public bool ContainsPrefix(string prefix) {
            return prefix.ToLower().IndexOf("country") > -1;
        }
        public ValueProviderResult GetValue(string key) {
            if (ContainsPrefix(key))
                return new ValueProviderResult("China", "China", CultureInfo.InvariantCulture);
            else
                return null;
        }
    }

    这就自定义好了一个 Value Provider,当需要一个 Country 的值时,它始终返回"China",其它返回 null。ValueProviderResult 类的构造器有三个参数,第一个参数是原始值对象,第二个参数是原始对象的字符串表示,最后一个是转换这个值所关联的 culture 信息。

    为了让 Model Binder 调用这个 Value Provider,我们需要创建一个能实现化它的类,这个类需要继承  ValueProviderFactory 抽象类。如下我们创建一个这样的类,名为 CustomValueProviderFactory:

    public class CustomValueProviderFactory : ValueProviderFactory {
        public override IValueProvider GetValueProvider(ControllerContext controllerContext) {
            return new CountryValueProvider();
        }
    }

    当 model binder 在绑定的过程中需要获取值时会调用这里的 GetValueProvider 方法。这里我们没有做别的处理,直接返回了一个 CountryValueProvider 实例。

    最后我们需要在 Global.asax 文件中的 Application_Start 方法中进行注册,如下:

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        ValueProviderFactories.Factories.Insert(0, new CustomValueProviderFactory());
    ...

    自定义 Model Binder

    我们也可以为特定的 Model 自定义 Model Binder。前面讲了默认的 Model Binder 实现的接口是 IModelBinder(前文列出了它的定义),自定义的 Binder 自然也需要实现该接口。下面我们在 Infrastructure 文件夹中添加一个实现了该接口的名为  AddressBinder 类,代码如下:

    public class AddressBinder : IModelBinder {
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
            Address model = (Address)bindingContext.Model ?? new Address();
            model.City = GetValue(bindingContext, "City");
            model.Country = GetValue(bindingContext, "Country");
            return model;
        }
    
        private string GetValue(ModelBindingContext context, string name) {
            name = (context.ModelName == "" ? "" : context.ModelName + ".") + name;
            ValueProviderResult result = context.ValueProvider.GetValue(name);
            if (result == null || result.AttemptedValue == "") 
                return "<Not Specified>";
            else 
                return (string)result.AttemptedValue;
        }
    }

    当 MVC 框架需要一个 model 类型的实现时,则调用 BindModel 方法。它的 ControllerContext 类型参数提供请求相关的上下文信息,ModelBindingContext 类型参数提供 model 对象相关的上下文信息。ModelBindingContext 常用的属性有Model、ModelName、ModelType 和 ValueProvider。这里的 GetValue 方法用到的 context.ModelName 属性可以告诉我们,如果有前缀(一般指复合类型名),则需要把它加在属性名的前面,这样 MVC 才能获取到以 [0].City、[0].Country 名称传递的值。

    然后我们需要在 Global.asax 的 Application_Start 方法中对自定义的 Model Binder 进行注册,如下所示:

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        //ValueProviderFactories.Factories.Insert(0, new CustomValueProviderFactory());
        ModelBinders.Binders.Add(typeof(Address), new AddressBinder());
    ...
  • 相关阅读:
    一个完整的Oracle建表的例子
    【转】oracle 体系结构
    JMeter-Window10系统下设置环境变量
    JMeter 3.0 POST Body Data 中文乱码问题
    JMeter接口测试报错,反馈和postman不一样(二)
    JMeter参数文件的相对路径
    JMeter正则表达式提取器说明
    JMeter接口测试报错,反馈和postman不一样(一)
    协程实现多边同时交互原理
    python 多线程中子线程和主线程相互通信
  • 原文地址:https://www.cnblogs.com/ilooking/p/4089953.html
Copyright © 2020-2023  润新知