• Model Binding in ASP.NET MVC


          Request被处理到ActionInvoker时,ActionInvoker找到目标Action,方法列表的参数是怎么传递的? 这就需要理解Model Binding过程了。

          看一个普通的action:

       public ViewResult Person(int id)
       {
           var myPerson = new Person(); 
           return View(myPerson);
       }

    请求http://mydomain.com/Home/Person/1 经过Route系统解析后,执行到Person。id的参数值解析过程为: Request.Form["id"] -> RouteData.Values["id"] -> Request.QueryString["id"] -> Request.Files["id"]。 当然了,这里解析完第RouteData后就找到目标,退出后边的解析了。

         看参数的一般解析过程:

           

     执行过程从上到下,找到即中止,直到找到;否则,如果参数为值类型,并且必选,则报错。另外,参数解析过程中,对于简单类型,DefaultModelBinder利用System.ComponentModel.TypeDescriptor类将解析出的字符串转换为目标类型。如果转换失败,model binding失败。

          对于上述Person方法,加入对应的view为:

    @using ModelBinding.Models

    @model Person
               
    @{
        ViewBag.Title = "Person";
        Layout = "~/Views/Shared/_Layout.cshtml";
    }

    @{
        var myPerson = new Person() {FirstName = "Jane", LastName = "Doe"};
    }
    @using (Html.BeginForm()) {
        @Html.EditorFor(m => myPerson)
        @Html.EditorForModel()
        <input type="submit" value="Submit" />
    }

    标黄部分,实际上render了2个Person对象实例。那么它的postback对应的action方法怎么写?

            [HttpPost]
            public ActionResult Person(Person firstPerson, [Bind(Prefix = "myPerson")] Person secondPerson) //Exclude="IsApproved, Role" //FirstName,LastName
            {
                //do sth.
                return Content("Success");
            }

    它可以接收2个Person实例参数。其中,第二个参数加了一个Bind设置,Prefix后还可以加入Exclude、Include设置。它们分别表示model所绑定的数据来源前缀、忽略赋值的属性、必须赋值的属性。

         如果要传值多个字符串,因为是同类型,可以讲它们作为数组传值么?先看view:

    Enter your three favorite movies:
    @using (Html.BeginForm()) {
        @Html.TextBox("movies""m1"new{id="m1", name="m1"})
        @Html.TextBox("movies""m2"new { id = "m2", name = "m2" })
        @Html.TextBox("movies""m3"new { id = "m3", name="m3" })
        <input type="submit" />
    }

    对应的action为:

    [HttpPost]
    public ActionResult Movies(string[] movies)
    {
        return Content("done");
    }

    测试成功。Asp.net MVC Framework貌似足够强大,它model binding系统能够智能识别和提取model参数。看model为多个对象实例的list时:

    @model List<ModelBinding.Models.Person>
    @{
        ViewBag.Title = "PersonList";
        Layout = "~/Views/Shared/_Layout.cshtml";
    }

    @using (Html.BeginForm())
    {
        for (int i = 0; i < Model.Count; i++)
        {
            <h4>Person Number: @i</h4>
            @:First Name: @Html.EditorFor(m => m[i].FirstName)
            @:Last Name: @Html.EditorFor(m => m[i].LastName)
        }
        <input type="submit"/>
    }

    对应的action为:

            [HttpPost]
            public ActionResult PersonList(List<Person> list)
            {
                return Content("done");
            }

    view中的多个person配置,在页面被postback会server后,就被model binding为List<Person>类型的list了。
        如果要手动来触发绑定,改进如下:

            [HttpPost]
            [ActionName("PersonList")]
            public ActionResult PersonListResult()
            {
                var list = new List<Person>();//Person myPerson = (Person)DependencyResolver.Current.GetService(typeof(Person));
                UpdateModel(list);

                return Content("done");
            }

    可以看到,参数占位已经被移除,然后在action内部,先创建一个list,然后调用controller基类的UpdateModel方法来更新list。而UpdateModel方法作用就是根据request传回来的值,同步model。基于之前讲述的model binding寻值的4个源,这里可以直接配置数据源。将上面标黄的代码改为:

       UpdateModel(list, new FormValueProvider(ControllerContext));

    即可。这时,model数据源直接从Form中获取。

         因为FormCollection实现了IValueProvider接口,上面的实现还可以继续改进为:

            [HttpPost]
            [ActionName("PersonList")]
            public ActionResult PersonListResult(FormCollection formData)
            {
                var list = new List<Person>();
                //UpdateModel(list, formData);

                if(TryUpdateModel(list, formData))
                {
                    return Content("done");
                }
                else
                {                   
                    //...provide UI feedback based on ModelState
                    
    //var isValid = ModelState.IsValid;
                    return View();
                }

                //return Content("done");
            }        

    其中,TryUpdateModel方法可以避免binding失败时抛出异常。

         利用Model Binding,怎么来上传文件?如下:

    <form action="@Url.Action("UploadFile")" method="post" enctype="multipart/form-data">
        Upload a photo: <input type="file" name="photo" />
        <input type="submit" />
    </form>

     action的写法:

            [HttpPost]
            public ActionResult UploadFile(HttpPostedFileBase file)
            {
                // Save the file to disk on the server
                string filename = "myfileName"// ... pick a filename
                file.SaveAs(filename);

                //// ... or work with the data directly
                //byte[] uploadedBytes = new byte[file.ContentLength];
                
    //file.InputStream.Read(uploadedBytes, 0, file.ContentLength);
                //// Now do something with uploadedBytes

                return Content("done");
            }

         

          最后,来自己定义Model Binding系统吧。先基于IValueProvider接口,实现一个:

        public class CurrentTimeValueProvider : IValueProvider
        {
            public bool ContainsPrefix(string prefix)
            {
                return string.Compare("CurrentTime", prefix, true) == 0;
            }

            public ValueProviderResult GetValue(string key)
            {
                return ContainsPrefix(key)
                           ? new ValueProviderResult(DateTime.Now, null, CultureInfo.InvariantCulture)
                           : null;
            }
        }

    创建一个factory:

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

    Application_Start中注册:

        ValueProviderFactories.Factories.Insert(0new CurrentTimeValueProviderFactory());

    因为它的位置为0,优先被利用。看一个action:

            public ActionResult Clock(DateTime currentTime)
            {
                return Content("The time is " + currentTime.ToLongTimeString());
            } 

    显然,它需要一个datetime参数,但是通过http://mydomain.com/home/clock/ 可以访问,这就是CurrentTimeValueProvider的功劳。

         自定义一个Dependency-Aware(不知道怎么翻译)的ModelBinder:

        public class DIModelBinder : DefaultModelBinder
        {
            protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
            {
                return DependencyResolver.Current.GetService(modelType) ??
                       base.CreateModel(controllerContext, bindingContext, modelType);
            }
        }

     注册使用:

       ModelBinders.Binders.DefaultBinder = new DIModelBinder();

    为Person创建一个专用的ModelBinder:

        public class PersonModelBinder : IModelBinder
        {
            public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
            {
                // see if there is an existing model to update and create one if not
                Person model = (Person)bindingContext.Model ?? (Person)DependencyResolver.Current.GetService(typeof(Person));

                // find out if the value provider has the required prefix
                bool hasPrefix = bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName);
                string searchPrefix = (hasPrefix) ? bindingContext.ModelName + "." : "";

                // populate the fields of the model object
                model.PersonId = int.Parse(GetValue(bindingContext, searchPrefix, "PersonId"));
                model.FirstName = GetValue(bindingContext, searchPrefix, "FirstName");
                model.LastName = GetValue(bindingContext, searchPrefix, "LastName");
                model.BirthDate = DateTime.Parse(GetValue(bindingContext, searchPrefix, "BirthDate"));
                model.IsApproved = GetCheckedValue(bindingContext, searchPrefix, "IsApproved");
                model.Role = (Role)Enum.Parse(typeof(Role), GetValue(bindingContext, searchPrefix, "Role"));

                return model;
            }

            private string GetValue(ModelBindingContext context, string prefix, string key)
            {
                ValueProviderResult vpr = context.ValueProvider.GetValue(prefix + key);
                return vpr == null ? null : vpr.AttemptedValue;
            }

            private bool GetCheckedValue(ModelBindingContext context, string prefix, string key)
            {
                bool result = false;
                ValueProviderResult vpr = context.ValueProvider.GetValue(prefix + key);
                if (vpr != null)
                {
                    result = (bool)vpr.ConvertTo(typeof(bool));
                }
                return result;
            }
        }

    注册使用:

       ModelBinders.Binders.Add(typeof(Person), new PersonModelBinder());

    自定义一个ModelBindProvider:

        public class CustomModelBinderProvider : IModelBinderProvider
        {
            public IModelBinder GetBinder(Type modelType)
            {
                return modelType == typeof(Person) ? new PersonModelBinder() : null;
            }
        }

    注册使用:

        ModelBinderProviders.BinderProviders.Add(new CustomModelBinderProvider());

    基于上面的代码,你也可以直接给对象类加上Model Binding:

        [ModelBinder(typeof(PersonModelBinder))]

        public class Person 


         源码download 

  • 相关阅读:
    FirstAFNetWorking
    JSONModel 简单例子
    KVO
    KVC
    关于UITableView的性能优化(历上最全面的优化分析)
    浅拷贝和深拷贝
    UI2_异步下载
    UI2_同步下载
    算法图解学习笔记02:递归和栈
    算法图解学习笔记01:二分查找&大O表示法
  • 原文地址:https://www.cnblogs.com/Langzi127/p/2746953.html
Copyright © 2020-2023  润新知