asp.net mvc源码分析-ActionResult篇 RazorView.RenderView
接着上文asp.net mvc源码分析-ActionResult篇 FindView 我们已经创建好view了,大家还记得在BuildManagerCompiledView的Render方法中最后调用的是RenderView。可能是跟人喜好问题,还有就是我工作项目用到的多数是Razor,所以这里就讲讲RazorView吧。
想让我们可看看RazorView的构造函数有什么特别的地方
public RazorView(ControllerContext controllerContext, string viewPath, string layoutPath, bool runViewStartPages, IEnumerable<string> viewStartFileExtensions, IViewPageActivator viewPageActivator)
: base(controllerContext, viewPath, viewPageActivator) {
LayoutPath = layoutPath ?? String.Empty;
RunViewStartPages = runViewStartPages;
StartPageLookup = StartPage.GetStartPage;
ViewStartFileExtensions = viewStartFileExtensions ?? Enumerable.Empty<string>();
}
其中LayoutPath 就是我们的masterPath,RunViewStartPages =true,ViewStartFileExtensions =FileExtensions,viewPageActivator=DefaultViewPageActivator的一个实例,viewPageActivator的设置在父类BuildManagerCompiledView的构造函数中设置。现在让我们看看RenderView这个方法:
protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance) { if (writer == null) { throw new ArgumentNullException("writer"); } WebViewPage webViewPage = instance as WebViewPage; if (webViewPage == null) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentCulture, MvcResources.CshtmlView_WrongViewBase, ViewPath)); } // An overriden master layout might have been specified when the ViewActionResult got returned. // We need to hold on to it so that we can set it on the inner page once it has executed. webViewPage.OverridenLayoutPath = LayoutPath; webViewPage.VirtualPath = ViewPath; webViewPage.ViewContext = viewContext; webViewPage.ViewData = viewContext.ViewData; webViewPage.InitHelpers(); WebPageRenderingBase startPage = null; if (RunViewStartPages) { startPage = StartPageLookup(webViewPage, RazorViewEngine.ViewStartFileName, ViewStartFileExtensions); } webViewPage.ExecutePageHierarchy(new WebPageContext(context: viewContext.HttpContext, page: null, model: null), writer, startPage); }
首先把当前view所对应的类型实例转化为WebViewPage,转换失败则抛出异常。WebViewPage的继承结构如下:WebViewPage-》WebPageBase-》WebPageRenderingBase-》WebPageExecutingBase。
记下来设置webViewPage的几个重要属性
webViewPage.OverridenLayoutPath = LayoutPath;
webViewPage.VirtualPath = ViewPath;
webViewPage.ViewContext = viewContext;
webViewPage.ViewData = viewContext.ViewData;
然后调用webViewPage.InitHelpers()
public virtual void InitHelpers() {
Ajax = new AjaxHelper<object>(ViewContext, this);
Html = new HtmlHelper<object>(ViewContext, this);
Url = new UrlHelper(ViewContext.RequestContext);
}
设置 Ajax,Html,Url3个属性
默认 情况下RunViewStartPages为true。
startPage = StartPageLookup(webViewPage, RazorViewEngine.ViewStartFileName, ViewStartFileExtensions);
其中 ViewStartFileName = "_ViewStart";FileExtensions = new[] { "cshtml","vbhtml",};
这里 多说明一下StartPage直接继承于WebPageRenderingBase,我们还是来看看它的GetStartPage是怎么实现的吧:
public static WebPageRenderingBase GetStartPage(WebPageRenderingBase page, string fileName, IEnumerable<string> supportedExtensions) { if (page == null) { throw new ArgumentNullException("page"); } if (String.IsNullOrEmpty(fileName)) { throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, CommonResources.Argument_Cannot_Be_Null_Or_Empty, "fileName"), "fileName"); } if (supportedExtensions == null) { throw new ArgumentNullException("supportedExtensions"); } // Build up a list of pages to execute, such as one of the following: // ~/somepage.cshtml // ~/_pageStart.cshtml --> ~/somepage.cshtml // ~/_pageStart.cshtml --> ~/sub/_pageStart.cshtml --> ~/sub/somepage.cshtml WebPageRenderingBase currentPage = page; var pageDirectory = VirtualPathUtility.GetDirectory(page.VirtualPath); // Start with the requested page's directory, find the init page, // and then traverse up the hierarchy to find init pages all the // way up to the root of the app. while (!String.IsNullOrEmpty(pageDirectory) && pageDirectory != "/" && Util.IsWithinAppRoot(pageDirectory)) { // Go through the list of support extensions foreach (var extension in supportedExtensions) { var path = VirtualPathUtility.Combine(pageDirectory, fileName + "." + extension); if (currentPage.FileExists(path, useCache: true)) { var factory = currentPage.GetObjectFactory(path); var parentStartPage = (StartPage)factory(); parentStartPage.VirtualPath = path; parentStartPage.ChildPage = currentPage; currentPage = parentStartPage; break; } } pageDirectory = currentPage.GetDirectory(pageDirectory); } // At this point 'currentPage' is the root-most StartPage (if there were // any StartPages at all) or it is the requested page itself. return currentPage; }
首先 WebPageRenderingBase currentPage = page;这句就不说了; var pageDirectory = VirtualPathUtility.GetDirectory(page.VirtualPath)是返回VirtualPath所对应的目录,举个例子吧,VirtualPath=~/Views/Home/Index.cshtml,那么pageDirectory=~/Views/Home/,那么现在就应该进入while循环了,
var path = VirtualPathUtility.Combine(pageDirectory, fileName + "." + extension);生成新的起始页的path,path=~/Views/Home/_ViewStart.cshtml,很显然这个文件不存在。内部的if语句无法执行。这个foreach是循环的文件扩展名,在实际的项目开发中可以考虑将FileExtensions = new[] { "cshtml","vbhtml",};中2个元素移除一个以提高性能。
第二次计入while时pageDirectory=~/Views/,那么现在对应的path=~/Views/_ViewStart.cshtml我们知道这个文件默认是存在的。
var factory = currentPage.GetObjectFactory(path);
var parentStartPage = (StartPage)factory();
parentStartPage.VirtualPath = path;
parentStartPage.ChildPage = currentPage;
currentPage = parentStartPage;
这2句也很好理解不过具体实现就很复杂了,根据当前的path新建的StartPage,并设置它的VirtualPath、ChildPage ,把它作为返回值。
直到pageDirectory=/才推出这个while循环。从这里我们知道_ViewStart可以嵌套另一个_ViewStar,有点像我们的view有自己的Layout,而Layout对应的view也有Layout层层递归。
最后调用 webViewPage.ExecutePageHierarchy(new WebPageContext(context: viewContext.HttpContext, page: null, model: null), writer, startPage);
这里创建了一个WebPageContext,WebPageContext也没什么特别的地方。ExecutePageHierarchy的具体定义是在WebPageBase中,
public void ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage) { PushContext(pageContext, writer); if (startPage != null) { if (startPage != this) { var startPageContext = Util.CreateNestedPageContext<object>(parentContext: pageContext, pageData: null, model: null, isLayoutPage: false); startPageContext.Page = startPage; startPage.PageContext = startPageContext; } startPage.ExecutePageHierarchy(); } else { ExecutePageHierarchy(); } PopContext(); }
首先在方法开始的地方先保存pageContext和writer,在方法结束前在弹出。默认情况下我们会新建一个WebPageContext作为page的PageContext属性,最后调用startpage的
ExecutePageHierarchy方法。该方法的实现在StartPage中
public override void ExecutePageHierarchy() { // Push the current pagestart on the stack. TemplateStack.Push(Context, this); try { // Execute the developer-written code of the InitPage Execute(); // If the child page wasn't explicitly run by the developer of the InitPage, then run it now. // The child page is either the next InitPage, or the final WebPage. if (!RunPageCalled) { RunPage(); } } finally { TemplateStack.Pop(Context); } } public void RunPage() { RunPageCalled = true; //ChildPage.PageContext = PageContext; ChildPage.ExecutePageHierarchy(); } public void RunPage() { RunPageCalled = true; //ChildPage.PageContext = PageContext; ChildPage.ExecutePageHierarchy(); }
这里的 Execute();是真正调用_ViewStart.cshtml ,在 RunPage方法中有 ChildPage.ExecutePageHierarchy(), 实际是要调用WebViewPage的ExecutePageHierarchy方法。
public override void ExecutePageHierarchy() {
// Change the Writer so that things like Html.BeginForm work correctly
ViewContext.Writer = Output;
base.ExecutePageHierarchy();
// Overwrite LayoutPage so that returning a view with a custom master page works.
if (!String.IsNullOrEmpty(OverridenLayoutPath)) {
Layout = OverridenLayoutPath;
}
}
可以见render一个page是多么的复杂啊。具体实现我们就不关心了,我们只要知道在RenderView时是递归render相应的view。我们只要知道在RenderView时是递归render相应的view。 同时我们需要知道在一次完整的http请求过程中_ViewStart.cshtml是最先执行的,_Layout.cshtml是最后执行