您的位置:首页 > 编程语言 > ASP

Model Binding in ASP.NET MVC

2012-10-30 19:59 507 查看
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(0, new 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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: