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后就找到目标,退出后边的解析了。

     看参数的一般解析过程:

      Model Binding in ASP.NET MVC_第1张图片 

 执行过程从上到下,找到即中止,直到找到;否则,如果参数为值类型,并且必选,则报错。另外,参数解析过程中,对于简单类型,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 

 

 

 

 

 

 

 

你可能感兴趣的:(asp.net)