带你读开源—ASP.NET_MVC(十二)

rufengit 2016-09-14 07:54:17
今天我们主要聊一聊WebViewPage这个类,为什么突然要研究这个类呢?大家还记得View中Razor视图引擎的.cshtml文件吧?它被MVC即时翻译成C#的类,这个类继承于WebViewPage类。对于强类型的Razor视图,.cshtml文件被翻译成WebViewPage<TModel>泛型类,我们之所以能够在视图页面顶部用形如“@model string[]”的东东定义强类型的View,是因为MVC把其认为是WebViewPage<TModel>泛型类的类型参数;弱类型翻译成WebViewPage非泛型类。
在强类型视图中,我们可以应“@model”关键字定义强类型,并用“@Model”关键字引用Action传递过来的模型实例。很多人特别是初学者对这一点很是费解,甚至经常搞混到底什么时候用小写的model,什么时候用大写的Model,这里我们探寻一下它的来龙去脉。
我们来看一下为什么能在自己的cshtml文件中访问Model,这是因为WebViewPage<TModel>的定义中有名称为Model的属性(代码段1),而我们自己写的Razor视图类又被翻译成WebViewPage<TModel>的子类,当然可以访问Model成员了。
        public new TModel Model
{
get { return ViewData.Model; }
}

代码段 1

为什么在Razor中可以直接用“Model”来代表Action传递过来的模型呢?我们看位于System.Web.Mvc下的Controller类中View方法的定义,见代码段2,这个View方法就是我们在Action中常用的那个用来返回ActionResult的方法。可以看到代码段2把Action中的模型传给了ViewData.Model,而代码段1又把ViewData.Model传给了Model,所以可以在Razor视图中用Model访问Action传过来的model。
        protected internal virtual ViewResult View(string viewName, string masterName, object model)
{
if (model != null)
{
ViewData.Model = model;
}

return new ViewResult
{
ViewName = viewName,
MasterName = masterName,
ViewData = ViewData,
TempData = TempData,
ViewEngineCollection = ViewEngineCollection
};
}

代码段 2

按照ASP.NET MVC的大概请求处理管线流程,即“Request->Action->View”,我们需要强调两个概念。一个是刚才讨论的从Action到View的Model传递,另一个是从Request到Action的参数绑定,也就是所谓的“模型绑定(ModelBinding)”。
举个例子吧,见代码段3。
        public ActionResult ProductDetail (int productID)
{
return View();
}

代码段 3

当我们访问地址“/Home/ProductDetail?productID=1”的时候,MVC会自动把查询字符串中productID的值传递给ProductDetail这个Action作为参数使用。当然,查询字符串只是MVC查找Action参数的一个选择,还可能从RouteData、Request.Form等HTTP请求信息中查找。那么,MVC究竟是如何把Request和Action参数联系到一起呢?这就是ModelBinding的作用所在,下面我们一探究竟。
在MVC源码中,找到System.Web.ControllerActionInvoker类中的InvokeAction方法,这个方法是MVC调用Action方法的地方,代码段4是该方法的一部分。MVC首先根据ActionName找到相应的Action方法,再用GetParameterValues方法取得Action所需要的参数,从而完成对Action的调用。
IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);

代码段 4

那么MVC是如何取得Action参数的呢?我们找到GetParameterValues方法的定义(代码段5)。这个方法的脉络比较清晰,首先用actionDescriptor.GetParameters()方法获得目标Action的形参,然后在foreach循环中,针对每一个形参,用GetParameterValue方法取得对应实参,并存储在一个字典中。
        protected virtual IDictionary<string, object> GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
Dictionary<string, object> parametersDict = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
ParameterDescriptor[] parameterDescriptors = actionDescriptor.GetParameters();

foreach (ParameterDescriptor parameterDescriptor in parameterDescriptors)
{
parametersDict[parameterDescriptor.ParameterName] = GetParameterValue(controllerContext, parameterDescriptor);
}
return parametersDict;
}

代码段 5

代码段5中的关键是GetParameterValue方法获取实参的过程,我们转到该方法的定义(代码段6),原来MVC通过调用IModelBinder接口实例的BindModel方法把Request和Action参数关联起来,并返回一个object类型对象。
        protected virtual object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
{
// collect all of the necessary binding properties
Type parameterType = parameterDescriptor.ParameterType;
IModelBinder binder = GetModelBinder(parameterDescriptor);
IValueProvider valueProvider = controllerContext.Controller.ValueProvider;
string parameterName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName;
Predicate<string> propertyFilter = GetPropertyFilter(parameterDescriptor);

// finally, call into the binder
ModelBindingContext bindingContext = new ModelBindingContext()
{
FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterType),
ModelName = parameterName,
ModelState = controllerContext.Controller.ViewData.ModelState,
PropertyFilter = propertyFilter,
ValueProvider = valueProvider
};

object result = binder.BindModel(controllerContext, bindingContext);
return result ?? parameterDescriptor.DefaultValue;
}

代码段 6

...全文
1191 10 打赏 收藏 转发到动态 举报
写回复
用AI写文章
10 条回复
切换为时间正序
请发表友善的回复…
发表回复
qq_36184000 2016-09-19
  • 打赏
  • 举报
回复
DING DD DDDDDDDDDDDDDDD
a5677456 2016-09-18
  • 打赏
  • 举报
回复
厉害哦
a5677456 2016-09-18
  • 打赏
  • 举报
回复
爱睡觉的阿狸 2016-09-16
  • 打赏
  • 举报
回复
insus 2016-09-15
  • 打赏
  • 举报
回复
关注,研究与学习中......

62,046

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术交流专区
javascript云原生 企业社区
社区管理员
  • ASP.NET
  • .Net开发者社区
  • R小R
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

.NET 社区是一个围绕开源 .NET 的开放、热情、创新、包容的技术社区。社区致力于为广大 .NET 爱好者提供一个良好的知识共享、协同互助的 .NET 技术交流环境。我们尊重不同意见,支持健康理性的辩论和互动,反对歧视和攻击。

希望和大家一起共同营造一个活跃、友好的社区氛围。

试试用AI创作助手写篇文章吧