今天我们主要聊一聊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 ViewResultView(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.NETMVC的大概请求处理管线流程,即“Request->Action->View”,我们需要强调两个概念。一个是刚才讨论的从Action到View的Model传递,另一个是从Request到Action的参数绑定,也就是所谓的“模型绑定(ModelBinding)”。
举个例子吧,见代码段3。
public ActionResult ProductDetail (intproductID) { 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); ActionExecutedContextpostActionContext = InvokeActionMethodWithFilters(controllerContext,filterInfo.ActionFilters, actionDescriptor, parameters);
代码段 4
那么MVC是如何取得Action参数的呢?我们找到GetParameterValues方法的定义(代码段5)。这个方法的脉络比较清晰,首先用actionDescriptor.GetParameters()方法获得目标Action的形参,然后在foreach循环中,针对每一个形参,用GetParameterValue方法取得对应实参,并存储在一个字典中。
protected virtualIDictionary<string, object> GetParameterValues(ControllerContextcontrollerContext, ActionDescriptor actionDescriptor) { Dictionary<string, object>parametersDict = new Dictionary<string,object>(StringComparer.OrdinalIgnoreCase); ParameterDescriptor[]parameterDescriptors = actionDescriptor.GetParameters(); foreach (ParameterDescriptorparameterDescriptor in parameterDescriptors) { parametersDict[parameterDescriptor.ParameterName] =GetParameterValue(controllerContext, parameterDescriptor); } return parametersDict; }
代码段 5
代码段5中的关键是GetParameterValue方法获取实参的过程,我们转到该方法的定义(代码段6),原来MVC通过调用IModelBinder接口实例的BindModel方法把Request和Action参数关联起来,并返回一个object类型对象。
protected virtual objectGetParameterValue(ControllerContext controllerContext, ParameterDescriptorparameterDescriptor) { // collect all of the necessarybinding 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 prefixnot 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