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

        今天我们主要聊一聊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

 

你可能感兴趣的:(mvc,.net,开源,asp.net,开源代码)