上一篇了解了请求至Controller的Action过程,这篇继续看源码处理Action收到请求数据再返回ActionResult到View的过程。
本节要讨论的问题
- Action的传参过程
- ActionResult
- IView / IViewEngine / ViewEngineCollection / ViewEngineResult
记得上篇反编译源看到Filter的执行顺序提到命名1,2,3的变量,在MVC3的源码中这个微软改掉了。
AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor); ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters); ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);
一、Action的传参过程
我们在定义Action方法时常用的传参大致有这么几种,详细看代码注释
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Web; using System.Web.Mvc; using System.ComponentModel; using Demo.Model; using Demo.Service.IService; namespace Demo.Mvc.Controller { [HandleError] public class MenuController:Core.BaseController { private IMenuService service; public MenuController(IMenuService service) { this .service = service; } public ActionResult Index() { return View(); } [ActionName( " ShowId " )] public ActionResult ShowId( string id) { return View(service.Get(id)); } // UpdateModel 缺点失败会抛出异常 or TryUpdateModel方式会试转换时间整数等NULL字段 public ActionResult SaveModel( string name, string age) { var permisson = new INFRA_MENU_PERMISSION(); // UpdateModel<INFRA_MENU_PERMISSION>(permisson); TryUpdateModel(permisson); ViewData.Model = permisson; return View(); } // FormCollection 的方式 // 可以绑定任意集合类型:IList<Book>, ICollection<Book>, IEnumerable<Book>, List<Book>, Book[] 字典 public ActionResult SaveModel(FormCollection formCollection) { INFRA_MENU_PERMISSION permisson = new INFRA_MENU_PERMISSION(); permisson.ACTION_NAME = formCollection[ " ACTION_NAME " ]; permisson.CREATETIME = Convert.ToDateTime(formCollection[ " ACTION_NAME " ]); service.Update(permisson); return RedirectToAction( " Index " ); } // IModelBinder DefaultModelBinder将Request传递参数绑定到对象上 [OutputCache] public ActionResult SaveModel(INFRA_MENU_PERMISSION permisson) { service.Update(permisson); return RedirectToAction( " Index " ); } // 不绑定哪些属性 public ActionResult SaveModelWithExclude([Bind(Exclude = " CONTROLLER_NAME " )]INFRA_MENU_PERMISSION permisson) { service.Update(permisson); return RedirectToAction( " Index " ); } // 绑定哪些属性 public ActionResult SaveModelWithInclude([Bind(Include = " CONTROLLER_NAME " )]INFRA_MENU_PERMISSION permisson) { service.Update(permisson); return RedirectToAction( " Index " ); } } }
Action传参支持类型绑定,字典键,对象,集合等等。除了使用默认的绑定方式,还可以继承IModelBinder重写DefaultModelBinder。
自己实现一个ModelBinder的代码 以及处理绑定的Attribute (具体看代码注释),可以通过这种方式解决一些复杂对象的值绑定问题。
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.ComponentModel; namespace Demo.Mvc.ExModelBinder { // 实现类似DefaultModelBinder 参照一下DefaultModelBinder源码重写个ModelBinder // DefaultModelBinder 在MVC2和MVC3的源码是不同的,主要是ValueProvider这个字段 MVC2是字典个集合,MVC3是个策略模式 public class VOModelBinder : IModelBinder { #region IModelBinder 成员 public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { object model = Activator.CreateInstance(bindingContext.ModelType); PropertyDescriptorCollection col = TypeDescriptor.GetProperties(model); foreach (PropertyDescriptor item in col) { // ValueProvider记录Request参数键值对 var result = bindingContext.ValueProvider.GetValue(item.Name); if (result != null ) { var value = result.ConvertTo(item.PropertyType); item.SetValue(model, value); } } return model; // object model = Activator.CreateInstance(bindingContext.ModelType); // var properties = bindingContext.ModelType.GetProperties(); // foreach (var item in properties) // { // if (bindingContext.PropertyFilter(item.Name)) // { // var result = bindingContext.ValueProvider.GetValue(item.Name); // var value = result.ConvertTo(item.PropertyType); // item.SetValue(model, value, null); // } // } // return model; } #endregion } /// <summary> /// 选择ModelBinder的标记属性 (如果不通过属性标记,就要在Global全局文件中注册这个绑定) /// </summary> public class VOModelBinderAttribute : CustomModelBinderAttribute { public override IModelBinder GetBinder() { return new VOModelBinder(); } } }
使用自定义的ModelBinder处理参数转换对象有三种方式
1.Global全配置
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Web.Mvc; using System.Web.Routing; using System.Web; namespace Demo.Mvc { public class DemoApplication : HttpApplication { protected virtual void OnStart() { InitializeContainer(); RegisterRoutes(RouteTable.Routes); } private void InitializeContainer() { ControllerBuilder.Current.SetControllerFactory( new UnityControllerFactory(ContainerFactory.GetContainer())); } public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute( " {resource}.axd/{*pathInfo} " ); routes.MapRoute( " Default " , // Route name " {controller}/{action}/{id} " , // URL with parameters new { controller = " Menu " , action = " Index " , id = "" } // Parameter defaults ); } protected void Application_Start() { OnStart(); // 通过全局配置解决处理某个对象绑定使用的VOModelBinder ModelBinders.Binders.Add( typeof (Demo.Model.INFRA_MENU_PERMISSION), new ExModelBinder.VOModelBinder()); } } }
2.利用我们自定义的属性标记
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Web.Mvc; using Demo.Mvc.ExModelBinder; namespace Demo.Mvc.ViewObject { // [ModelBinder(typeof(VOModelBinder))] // 使用ModelBinderAttribute标记 [VOModelBinder] // 使用自定义VOModelBinderAttribute标记(属性类已指明使用VOModelBinder来处理数据绑定) public class UserVO { public string Name { set ; get ; } public bool Sex { set ; get ; } public string Address { set ; get ; } public DateTime Birthday { set ; get ; } } }
3.Action参数标记
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
public ActionResult Index([ExModelBinder.VOModelBinder]UserVO userVo) { return View(); }
以上就是Action参数和Request参数之间转换过程,使用默认的DefaultModelBinder会根据参数的复杂情况采用不同的方式处理参数。
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
public virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { if (bindingContext == null ) { throw new ArgumentNullException( " bindingContext " ); } bool performedFallback = false ; if ( ! String.IsNullOrEmpty(bindingContext.ModelName) && ! bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)) { // We couldn't find any entry that began with the prefix. If this is the top-level element, fall back // to the empty prefix. if (bindingContext.FallbackToEmptyPrefix) { bindingContext = new ModelBindingContext() { ModelMetadata = bindingContext.ModelMetadata, ModelState = bindingContext.ModelState, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider }; performedFallback = true ; } else { return null ; } } // Simple model = int, string, etc.; determined by calling TypeConverter.CanConvertFrom(typeof(string)) // or by seeing if a value in the request exactly matches the name of the model we're binding. // Complex type = everything else. if ( ! performedFallback) { bool performRequestValidation = ShouldPerformRequestValidation(controllerContext, bindingContext); ValueProviderResult vpResult = bindingContext.UnvalidatedValueProvider.GetValue(bindingContext.ModelName, skipValidation: ! performRequestValidation); if (vpResult != null ) { return BindSimpleModel(controllerContext, bindingContext, vpResult); // 简单参数处理---------- } } if ( ! bindingContext.ModelMetadata.IsComplexType) { return null ; } return BindComplexModel(controllerContext, bindingContext); // 复杂对象的处理-------------------------- }
而我们自定义处理参数的规则就由自己来写了。 了解这些参数转换问题,那这个过程是何时发生的?根据前篇文章的分析,这个过程肯定是发生在Controller 的策略ControllerActionInvoker在执行InvokeAction方法拦截Action的时候发生的,即在处理完IAuthorizationFilter过滤器之后,处理IActionFilter之前的这段代码
IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
不管使用哪种ModelBinder,通过GetParameterValues将Request 请求的参数转换对应的类型。看一下跟进的方法是如何处理的
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
// 获取参数信息的描述属性并遍历处理 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; } // 单独处理每种参数描术属性 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; }
主要业务都在第二个处理参数描述的取值的方法里,首先取得参数的类型,然后读取对应的ModelBinder,如果ModelBinder为空的时候则使用默认的DefaultModelBinder,接着生成ModelBindingContext上下文,然后又触发ModelBinder的BindModel方法,并将ModelBindingContext传递过去,
在BindModel中默认的或者自定义实现的对象属性赋值过程了,最后将生成的parameters对象集合再传回至要触发的Action方法中。
二、ActionResult
ActionResult是Action的返回结果。ActionResult 有多个派生类,每个子类功能均不同,并不是所有的子类都需要返回视图View,有些直接返回流,有些返回字符串等。我们来看一下ActionResult派生类关系图
![](https://i-blog.csdnimg.cn/blog_migrate/b21e38283657683a9cb09518215da2ee.png)
具体看一下每个类的功能,由于MSDN的示意图太简单不能完全表现所有的子类功能
类名 | 抽象类 | 父类 | 功能 |
ActionResult | abstract | Object | 顶层父类 |
ContentResult | 根据内容的类型和编码,数据内容.通过Controller的Content方法返回 | ||
EmptyResult | 返回空结果 | ||
FileResult | abstract | 写入文件内容,具体的写入方式在派生类中. | |
FileContentResult | FileResult | 通过 文件byte[] 写入Response 返回客户端,Controller的File方法 | |
FilePathResult | FileResult | 通过 文件路径 写入Response 返回客户端,Controller的File方法 | |
FileStreamResult | FileResult | 通过 Stream 写入Response 返回客户端,Controller的File方法 | |
HttpUnauthorizedResult | 抛出401错误 | ||
JavaScriptResult | 返回javascript文件 | ||
JsonResult | 返回Json格式的数据 | ||
RedirectResult | 使用Response.Redirect重定向页面 | ||
RedirectToRouteResult | 根据Route规则重定向页面 | ||
ViewResultBase | abstract | 调用IView.Render() 返回视图,两个常用属性ViewData,TempData | |
PartialViewResult | ViewResultBase | 调用父类ViewResultBase 的ExecuteResult方法. 重写了父类的FindView方法. 寻找用户控件.ascx文件 | |
ViewResult | ViewResultBase | 调用父类ViewResultBase 的ExecuteResult方法. Controller的View()方法默认封装ViewResult返回结果 |
简单的列几种写法,都是Controller已经封装好的
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
public ActionResult ShowContent() { return Content( " 测试ContentResult方法 " ); // 默认封装ContentResult文本返回 } public ActionResult Index(UserVO userVo) { return View(); // 默认封装ViewResult返回 } public ActionResult DownLoadFile( string fileName) { return File(Server.MapPath( @" /Images/view.jpg " ), @" image/gif " ); } public ActionResult ToOther( string fileName) { return Redirect( @" http://localhost:1847/Menu/ShowContent " ); }
当然你可以自己实现每种的类型返回,而不是通过Controller的方法返回。这个环节最重要的问题,当Action返回ActionResult后,这个ActionResult是如何工作的?ActionResult只有一个抽象方法 ExecuteResult ,当ActionResult实例被返回后,Controller执行器ControllerActionInvoker的InvokeAction方法在处理完IActionFilter之后调用了这段代码InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result);
看后两个参数,一个是IResultFilter过滤器,一个是Action返回的Result。该方法对返回ActionResult进行前置拦截后,接着调用ActionResult的ExecuteResult方法去处对应的响应业务(返回视图,或字符串,文件流等),最后又对ActionResult后置拦截了一次。调用棧比较深
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
// 1--------------------------------- protected virtual ResultExecutedContext InvokeActionResultWithFilters(ControllerContext controllerContext, IList < IResultFilter > filters, ActionResult actionResult) { ResultExecutingContext preContext = new ResultExecutingContext(controllerContext, actionResult); // InvokeActionResult 做为委托被前置与后置包围了 Func < ResultExecutedContext > continuation = delegate { InvokeActionResult(controllerContext, actionResult); return new ResultExecutedContext(controllerContext, actionResult, false /* canceled */ , null /* exception */ ); }; // need to reverse the filter list because the continuations are built up backward Func < ResultExecutedContext > thunk = filters.Reverse().Aggregate(continuation, (next, filter) => () => InvokeActionResultFilter(filter, preContext, next)); return thunk(); } // 2-------------------------------------------- internal static ResultExecutedContext InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func < ResultExecutedContext > continuation) { filter.OnResultExecuting(preContext); // 前置拦截---------------------------------------- if (preContext.Cancel) { return new ResultExecutedContext(preContext, preContext.Result, true /* canceled */ , null /* exception */ ); } bool wasError = false ; ResultExecutedContext postContext = null ; try { postContext = continuation(); // ActionResult的ExecuteResult的调用环节------------------------------------------ } catch (ThreadAbortException) { // This type of exception occurs as a result of Response.Redirect(), but we special-case so that // the filters don't see this as an error. postContext = new ResultExecutedContext(preContext, preContext.Result, false /* canceled */ , null /* exception */ ); filter.OnResultExecuted(postContext); // 出错了,后置拦截---------------------------------- throw ; } catch (Exception ex) { wasError = true ; postContext = new ResultExecutedContext(preContext, preContext.Result, false /* canceled */ , ex); filter.OnResultExecuted(postContext); if ( ! postContext.ExceptionHandled) { throw ; } } if ( ! wasError) { filter.OnResultExecuted(postContext); // 后置拦截---------------------------------------------- } return postContext; } // 3------------------------------------------------------------------ protected virtual void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult) { actionResult.ExecuteResult(controllerContext); }
注释的地方注意看一下,InvokeActionResult 是调用ActionResult.ExecuteResult的方法,被做为委托放到IResultFilter前后拦截法中间执行。Controller的执行器ControllerActionInvoker 这几个环节的调度者。再看一下ActionResult.ExecuteResult方法的业务,挑选个有代表性的子类实现的业务贴上来,
FileResult基类 的ExecuteResult,但又调用了WriteFile方法,这个方法又下放到子类中实现了
View Code
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
public override void ExecuteResult(ControllerContext context) { if (context == null ) { throw new ArgumentNullException( " context " ); } HttpResponseBase response = context.HttpContext.Response; response.ContentType = ContentType; if ( ! String.IsNullOrEmpty(FileDownloadName)) { // From RFC 2183, Sec. 2.3: // The sender may want to suggest a filename to be used if the entity is // detached and stored in a separate file. If the receiving MUA writes // the entity to a file, the suggested filename should be used as a // basis for the actual filename, where possible. string headerValue = ContentDispositionUtil.GetHeaderValue(FileDownloadName); context.HttpContext.Response.AddHeader( " Content-Disposition " , headerValue); } WriteFile(response); }
我们挑选FileStreamResult子类的WriteFile方法看看
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
protected override void WriteFile(HttpResponseBase response) { // grab chunks of data and write to the output stream Stream outputStream = response.OutputStream; using (FileStream) { byte [] buffer = new byte [_bufferSize]; while ( true ) { int bytesRead = FileStream.Read(buffer, 0 , _bufferSize); if (bytesRead == 0 ) { // no more data break ; } outputStream.Write(buffer, 0 , bytesRead); } } }
整个过程看下来FileStreamResult的ExecuteResult 将文件流写入HttpResponse中返回到客户端,而并不是返回视图。再看一下ViewResult的ExecuteResult的业务,这个业务是在父类的中实现的
ViewResultBase的ExecuteResult业务,ViewResultBase还有两个重要的性ViewData,TempData是在Acion返回的时候封装好的。
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
public override void ExecuteResult(ControllerContext context) { if (context == null ) { throw new ArgumentNullException( " context " ); } if (String.IsNullOrEmpty(ViewName)) { ViewName = context.RouteData.GetRequiredString( " action " ); } ViewEngineResult result = null ; if (View == null ) { result = FindView(context); View = result.View; } TextWriter writer = context.HttpContext.Response.Output; ViewContext viewContext = new ViewContext(context, View, ViewData, TempData, writer); View.Render(viewContext, writer); if (result != null ) { result.ViewEngine.ReleaseView(context, View); } }
先根据上下文中的路由+Action名找到对应的IView,然后调用IView的Render会出视图写入context.HttpContext.Response.Output返回到客户端。
三、IView / IViewEngine / ViewEngineCollection / ViewEngineResult
- IView 作用:展示View对象, 将页面读成流通过Writer写入Response中返回客户端,主要方法Render用来绘制DOM对象到流中。
- IViewEngine 作用:查找View对象(视图页面,例如aspx页面),但是返回结果是ViewEngineResult ,View被保存其中。主要方法FindView,FindPartialView。
- ViewEngineCollection 作用:视图引擎集合
- ViewEngineResult 作用:是IViewEngine查找View的结果
实现过程:当ViewResult执行ExecuteResult时会遍历ViewEngineCollection中所有IViewEngine 引擎,并调用每个IViewEngine 引擎的FindView,如果找到具体的View页面,则返回ViewEngineResult,ViewEngineResult包含了相关的IView信息,最后再调用IView的Render输出视图。
ViewEngineCollection 是视图引擎集合(当IView要Render视图的时候,要从视图引擎集合中选择一个视图引擎来实现),ViewEngineCollection是ViewResult的策略属性。可以通过属性注入的方式更换成其它视图引擎包括自定义的视图引擎。MVC3中ViewResult的属性ViewEngineCollection被默认注入了ViewEngines.Engines。
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
[SuppressMessage( " Microsoft.Usage " , " CA2227:CollectionPropertiesShouldBeReadOnly " , Justification = " This entire type is meant to be mutable. " )] public ViewEngineCollection ViewEngineCollection { get { return _viewEngineCollection ?? ViewEngines.Engines; } set { _viewEngineCollection = value; } }
再看一下ViewEngines.Engines类默认两个成员,一个处理aspx的WebFormViewEngine引擎,一个是MVC3新加入的RazorViewEngine引擎。
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
public static class ViewEngines { private readonly static ViewEngineCollection _engines = new ViewEngineCollection { new WebFormViewEngine(), new RazorViewEngine(), }; public static ViewEngineCollection Engines { get { return _engines; } } }
我们看一WebFormViewEngine类
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
public class WebFormViewEngine : BuildManagerViewEngine { public WebFormViewEngine() : this ( null ) { } public WebFormViewEngine(IViewPageActivator viewPageActivator) : base (viewPageActivator){ MasterLocationFormats = new [] { " ~/Views/{1}/{0}.master " , " ~/Views/Shared/{0}.master " }; AreaMasterLocationFormats = new [] { " ~/Areas/{2}/Views/{1}/{0}.master " , " ~/Areas/{2}/Views/Shared/{0}.master " , }; ViewLocationFormats = new [] { " ~/Views/{1}/{0}.aspx " , " ~/Views/{1}/{0}.ascx " , " ~/Views/Shared/{0}.aspx " , " ~/Views/Shared/{0}.ascx " }; AreaViewLocationFormats = new [] { " ~/Areas/{2}/Views/{1}/{0}.aspx " , " ~/Areas/{2}/Views/{1}/{0}.ascx " , " ~/Areas/{2}/Views/Shared/{0}.aspx " , " ~/Areas/{2}/Views/Shared/{0}.ascx " , }; PartialViewLocationFormats = ViewLocationFormats; AreaPartialViewLocationFormats = AreaViewLocationFormats; FileExtensions = new [] { " aspx " , " ascx " , " master " , }; } protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath) { return new WebFormView(controllerContext, partialPath, null , ViewPageActivator); } protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath) { return new WebFormView(controllerContext, viewPath, masterPath, ViewPageActivator); } }
里面已经指定了要查找View页的路径,主要是针对模板master,aspx,ascx等页面相对路径。再看他的爷爷类的FindView方法
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) { if (controllerContext == null ) { throw new ArgumentNullException( " controllerContext " ); } if (String.IsNullOrEmpty(viewName)) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, " viewName " ); } string [] viewLocationsSearched; string [] masterLocationsSearched; string controllerName = controllerContext.RouteData.GetRequiredString( " controller " ); string viewPath = GetPath(controllerContext, ViewLocationFormats, AreaViewLocationFormats, " ViewLocationFormats " , viewName, controllerName, _cacheKeyPrefix_View, useCache, out viewLocationsSearched); string masterPath = GetPath(controllerContext, MasterLocationFormats, AreaMasterLocationFormats, " MasterLocationFormats " , masterName, controllerName, _cacheKeyPrefix_Master, useCache, out masterLocationsSearched); if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && ! String.IsNullOrEmpty(masterName))) { return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched)); } return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this ); } private string GetPath(ControllerContext controllerContext, string [] locations, string [] areaLocations, string locationsPropertyName, string name, string controllerName, string cacheKeyPrefix, bool useCache, out string [] searchedLocations) { searchedLocations = _emptyLocations; if (String.IsNullOrEmpty(name)) { return String.Empty; } string areaName = AreaHelpers.GetAreaName(controllerContext.RouteData); bool usingAreas = ! String.IsNullOrEmpty(areaName); List < ViewLocation > viewLocations = GetViewLocations(locations, (usingAreas) ? areaLocations : null ); if (viewLocations.Count == 0 ) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, MvcResources.Common_PropertyCannotBeNullOrEmpty, locationsPropertyName)); } bool nameRepresentsPath = IsSpecificPath(name); string cacheKey = CreateCacheKey(cacheKeyPrefix, name, (nameRepresentsPath) ? String.Empty : controllerName, areaName); if (useCache) { return ViewLocationCache.GetViewLocation(controllerContext.HttpContext, cacheKey); } return (nameRepresentsPath) ? GetPathFromSpecificName(controllerContext, name, cacheKey, ref searchedLocations) : GetPathFromGeneralName(controllerContext, viewLocations, name, controllerName, areaName, cacheKey, ref searchedLocations); }
根据ControolerName+ActionName替换掉路径中{0}去查找对应的物理文件。第一次查找到了会保存到缓存中,第二次进来的时候直接从缓存中查找。
从Request->Controller->Action->ActionResult->View->Response过程到此就介绍完了。
突然想到一个问题,这个位置微软都使用了缓存,为什么每次DefaultControllerFactory都要直接反射Controller呢,而不一次反射就缓存下来提高效率,反而是让开发人员利用IOC容器去解决这个问题。 带这个疑问去看了MVC3的DefaultControllerFactory,这个问题已经解决了。DefaultControllerFactory新增了一个DependencyResolver来处理获取Controller的工作。但这个功能还是没有IOC容器的功能强大,因为IOC解决了构造注入的问题,而DependencyResolver则没有实现这个功能。.net mvc 已经整合了微软企业库很多内容,如果再把IOC功能直接整合进来,似乎企业库有点尴尬了。好了,这节就到这里了。相信你对.net MVC实现原理应该有了一定的了解。