asp.net mvc源碼分析-ActionResult篇 RazorView.RenderView

來源:互聯網
上載者:User

接著上文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是最後執行

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.