zoukankan      html  css  js  c++  java
  • 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是最后执行

     

  • 相关阅读:
    Gitlab安装之后不能正常启动案例解决
    SSH远程采用普通用户登录linux主机报错解决方法
    记一次AD域控客户端不能正常加域的故障处理
    Horizon7.9部署和克隆问题汇总
    VMware Guest customization fails on Linux
    Ubuntu18.04安装rabbitvcs svn图形化客户端和简单实用
    Ubuntu访问samba共享文件方法
    CentOS版本禁用Ctrl+Alt+Del重启功能
    02Kubernetes架构和组件
    01Kubernetes核心概念
  • 原文地址:https://www.cnblogs.com/majiang/p/2765261.html
Copyright © 2011-2022 走看看