上篇聊到ActionResult的ExecuteResult方法,今天继续。
我们首先看一下ActionResult的定义(代码段1),它是一个抽象类,只有一个抽象方法ExecuteResult。
public abstract class ActionResult { public abstract voidExecuteResult(ControllerContext context); }
代码段 1
ActionResult是个很重要的话题,MVC在Action中没有直接生成并回送HTML响应,而是返回一个ActionResult,再由MVC框架调用它的ExecuteResult方法。这是一种设计模式的应用,即“命令模式”,它把提出请求和执行请求分开,解除它们之间的耦合。有关命令模式,请自行找度娘脑补。
ActionResult有N多的子类,包括ViewResultBase、ContentResult、FileResult、HttpStatusCodeResult、JavaScriptResult、JsonResult、RedirectResult、RedirectToRouteResult、AtomEntryActionResult、AtomFeedActionResult、AtomServiceDocumentActionResult、DataContractJsonActionResult、DataContractXmlActionResult、MultiFormatActionResult、ResourceErrorActionResult、ResourceRedirectToRouteResult。这些子类都是针对不同的需求定义的,最常见的是ViewResultBase的子类ViewResult(当然,PartialViewResult也是ViewResultBase的子类)。不过ViewResult并没有重写ExecuteResult方法,而是由其父类ViewResultBase进行的重写,见代码段2。
public override voidExecuteResult(ControllerContext context) { if (context == null) { throw newArgumentNullException("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 = newViewContext(context, View, ViewData, TempData, writer); View.Render(viewContext, writer); if (result != null) { result.ViewEngine.ReleaseView(context, View); } }
代码段 2
代码段2中的语句【View.Render(viewContext, writer)】就是把HTML渲染并发回浏览器的核心代码。这里就开始引入万人敬仰的视图引擎机制了,我们看语句【result = FindView(context)】,跟踪到FindView的定义,哦,它是一个抽象方法,好吧,去他的子类ViewResult中找,见代码段3。
protected override ViewEngineResultFindView(ControllerContext context) { ViewEngineResult result =ViewEngineCollection.FindView(context, ViewName, MasterName); if (result.View != null) { return result; } // we need to generate an exceptioncontaining all the locations we searched StringBuilder locationsText = newStringBuilder(); foreach (string location inresult.SearchedLocations) { locationsText.AppendLine(); locationsText.Append(location); } throw newInvalidOperationException(String.Format(CultureInfo.CurrentCulture, MvcResources.Common_ViewNotFound, ViewName, locationsText)); }
代码段 3
FindView方法的作用是找到指定的视图引擎,在代码段3中找到语句【ViewEngineResult result = ViewEngineCollection.FindView(context,ViewName, MasterName)】,跟踪进入ViewEngineCollection.FindView的定义(代码段4)。
public virtual ViewEngineResultFindView(ControllerContext controllerContext, string viewName, stringmasterName) { if (controllerContext == null) { throw newArgumentNullException("controllerContext"); } if (String.IsNullOrEmpty(viewName)) { throw newArgumentException(MvcResources.Common_NullOrEmpty, "viewName"); } return Find(e =>e.FindView(controllerContext, viewName, masterName, true), e =>e.FindView(controllerContext, viewName, masterName, false)); }
代码段 4
跟踪进入Find方法(代码段5),这个方法的作用是在视图引擎集合中,用Func<IViewEngine,ViewEngineResult> lookup这个委托来搜索当前使用的视图引擎。
private ViewEngineResultFind(Func<IViewEngine, ViewEngineResult> lookup, bool trackSearchedPaths) { // Returns // 1st result // OR list of searched paths (iftrackSearchedPaths == true) // OR null ViewEngineResult result; List<string> searched = null; if (trackSearchedPaths) { searched = newList<string>(); } foreach (IViewEngine engine inCombinedItems) { if (engine != null) { result = lookup(engine); if (result.View != null) { return result; } if (trackSearchedPaths) { searched.AddRange(result.SearchedLocations); } } } if (trackSearchedPaths) { // Remove duplicate searchpaths since multiple view engines could have potentially looked at the samepath return newViewEngineResult(searched.Distinct().ToList()); } else { return null; } }
代码段 5
在代码段5中有一个CombinedItems,它代表视图引擎集合,它的定义见代码段6。
internal IViewEngine[] CombinedItems { get { IViewEngine[] combinedItems =_combinedItems; if (combinedItems == null) { combinedItems =MultiServiceResolver.GetCombined<IViewEngine>(Items, _dependencyResolver); _combinedItems =combinedItems; } return combinedItems; } }
代码段 6
CombinedItems实际上指的是ViewEngineCollection中的内容项,而ViewEngineCollection的内容又是在哪里填充的呢?我们看代码段3中的这句【ViewEngineResult result = ViewEngineCollection.FindView(context,ViewName, MasterName)】,里面有ViewEngineCollection,转到其定义,见代码段7。
[SuppressMessage("Microsoft.Usage","CA2227:CollectionPropertiesShouldBeReadOnly", Justification ="This entire type is meant to be mutable.")] public ViewEngineCollectionViewEngineCollection { get { return _viewEngineCollection?? ViewEngines.Engines; } set { _viewEngineCollection =value; } }
代码段 7
可见ViewEngineCollection来自于ViewEngines.Engines,接着转到ViewEngines.Engines的定义,见代码段8。看到了吧!原来秘密在此,ViewEngineCollection这个集合的内容为预置的WebFormViewEngine和RazorViewEngine,也就是我们所熟知的WebForm视图引擎和Razor视图引擎。
public static class ViewEngines { private static readonlyViewEngineCollection _engines = new ViewEngineCollection { new WebFormViewEngine(), new RazorViewEngine(), }; public static ViewEngineCollectionEngines { get { return _engines; } } }
代码段 8
我们再回看一下代码段4,可以看出【e => e.FindView(controllerContext, viewName, masterName, true)】这个Lamda表达式(本质是委托)作为Find方法的第一个参数lookup进行传递,也就是说,用这个Lamda表达式是判断某个视图引擎是不是当前使用的视图引擎。继续跟踪e.FindView,进入其定义(代码段9),发现FindView是接口IViewEngine的一个方法。IViewEngine是视图引擎的接口,我们已经到达视图引擎的核心了,鸡冻吧?
public interface IViewEngine { ViewEngineResultFindPartialView(ControllerContext controllerContext, string partialViewName,bool useCache); ViewEngineResultFindView(ControllerContext controllerContext, string viewName, stringmasterName, bool useCache); void ReleaseView(ControllerContextcontrollerContext, IView view); }
代码段 9
好了,我们看看都有谁实现了IViewEngine接口,找了半天发现只有VirtualPathProviderViewEngine实现了IViewEngine接口,见代码段10。
public virtual ViewEngineResultFindView(ControllerContext controllerContext, string viewName, stringmasterName, bool useCache) { if (controllerContext == null) { throw newArgumentNullException("controllerContext"); } if (String.IsNullOrEmpty(viewName)) { throw newArgumentException(MvcResources.Common_NullOrEmpty, "viewName"); } string[] viewLocationsSearched; string[] masterLocationsSearched; string controllerName =controllerContext.RouteData.GetRequiredString("controller"); string viewPath =GetPath(controllerContext, ViewLocationFormats, AreaViewLocationFormats,"ViewLocationFormats", viewName, controllerName, CacheKeyPrefixView,useCache, out viewLocationsSearched); string masterPath =GetPath(controllerContext, MasterLocationFormats, AreaMasterLocationFormats,"MasterLocationFormats", masterName, controllerName,CacheKeyPrefixMaster, useCache, out masterLocationsSearched); if (String.IsNullOrEmpty(viewPath)|| (String.IsNullOrEmpty(masterPath) &&!String.IsNullOrEmpty(masterName))) { return newViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched)); } return newViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this); }
代码段 10
跟踪CreateView方法,发现它是一个抽象方法,好吧,我们发现BuildManagerViewEngine继承了VirtualPathProviderViewEngine,但是它并没有重写了CreateView方法,不要慌,我们继续摸索,发现RazorViewEngine和WebFormViewEngine(先不管他)又继承了BuildManagerViewEngine,在这里我们只看RazorViewEngine的CreateView方法重写(代码段11)。
protected override IViewCreateView(ControllerContext controllerContext, string viewPath, stringmasterPath) { var view = new RazorView(controllerContext,viewPath, layoutPath: masterPath, runViewStartPages: true,viewStartFileExtensions: FileExtensions, viewPageActivator: ViewPageActivator) { DisplayModeProvider = DisplayModeProvider }; return view; }
代码段 11
代码段11得到了一个IView对象,进而使得代码段10得到一个ViewEngineResult,我们再转到代码段2中的语句【result = FindView(context);View = result.View;】,到此我们便得到了一个View对象,进而调用View.Render(viewContext, writer)方法,而其中的writer 又是什么东东?我们找到语句【TextWriter writer =context.HttpContext.Response.Output;】,豁然开朗不?我们久违的、日思夜想的Response终于出现了,有了它,我们就可以把HTML响应渲染回送到浏览器,至此,从浏览器发出HTTP请求,到服务器发回HTML响应,构成完整的回路,大功告成。我靠,累死我了!
未完待续。。。