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