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是最后执行

 

posted on 2012-11-11 16:50  dz45693  阅读(2298)  评论(0编辑  收藏  举报

导航