閱讀169 返回首頁    go 技術社區[雲棲]


ASP.NET MVC的Razor引擎:RazorView

Razor引擎具有兩個核心的類型,一個是表示View本身的類型RazorView,另一個則是獲取和創建它的RazorViewEngine,我們將用兩篇文章對它們分別進行剖析。Razor引擎下的View通過類型RazorView表示,它與表示Web Form引擎View的類型WebFormView都是BuildManagerCompiledView的子類。[本文已經同步到《How ASP.NET MVC Works?》中]

目錄
一、BuildManagerCompiledView
二、RazorView
三、實例演示:自定義View模擬RazorView的View呈現機製

為了能夠清楚地說明實現在BuildManagerCompiledView中的View激活與呈現機製,我們列出了BuildManagerCompiledView中與此相關的內部和受保護的成員。

   1: public abstract class BuildManagerCompiledView : IView
   2: {   
   3:     internal IViewPageActivator ViewPageActivator;
   4:   
   5:     protected BuildManagerCompiledView(ControllerContext controllerContext, string viewPath);
   6:     protected BuildManagerCompiledView(ControllerContext controllerContext, string viewPath, IViewPageActivator viewPageActivator);
   7:     internal BuildManagerCompiledView(ControllerContext controllerContext, string viewPath, IViewPageActivator viewPageActivator, IDependencyResolver dependencyResolver);
   8:    
   9:     public void Render(ViewContext viewContext, TextWriter writer);
  10:     protected abstract void RenderView(ViewContext viewContext, TextWriter writer, object instance);
  11:   
  12:     internal IBuildManager     BuildManager { get; set; }
  13:     public string              ViewPath { get; protected set; }
  14: }

通過《View編譯原理》的介紹我們知道采用Razor引擎的View文件(.cshtml或者.vbhtml)最終都會編譯成一個WebViewPage類型,所以通過RazorView/WebFormView體現的View的呈現機製最終體現在對WebViewPage對象的激活。我們可以利用BuildManager根據View文件的虛擬路徑得到編譯後的類型。從名稱也可以看出來,BuildManagerCompiledView內部就是利用了BuildManager根據指定的View文件虛擬路徑完成對WebViewPage對象激活。

BuildManagerCompiledView的屬性ViewPath表示的就是View文件的虛擬路徑,該屬性在構造函數中被初始化。BuildManagerCompiledView具有三個構造函數,對象本身的構造邏輯體現在內部構造函數上。如上麵的代碼片斷所示,除了將當前ControllerContext和View文件虛擬路徑作為構造函數的參數之外,該構造函數還具有額外兩個參數,其類型分別是IViewPageActivatorIDependencyResolver

   1: public interface IViewPageActivator
   2: {    
   3:     object Create(ControllerContext controllerContext, Type type);
   4: }

上麵的代碼片斷體現了接口IViewPageActivator的定義。顧名思義,該接口旨在實現對WebViewPage對象的激活,基於類型的對象激活機製實現在Create方法中。BuildManagerCompiledView的構造函數中指定的ViewPageActivator被用於初始化內部字段ViewPageActivator,如果沒有通過構造函數顯式指定ViewPageActivator對象,默認采用的是一個DefaultViewPageActivator對象。

DefaultViewPageActivator是一個具有如下定義的內部類型,我們可以看到它實際上依賴於一個DependencyResolver對象完成針對WebViewPage對象的激活。這個DependencyResolver對象可以通過構造函數進行顯式設置,而默認使用的DependencyResolver對象來源於DependencyResolver類型的靜態屬性Current。

   1: internal class DefaultViewPageActivator : IViewPageActivator
   2: {   
   3:     private Func<IDependencyResolver> _resolverThunk;  
   4:     public DefaultViewPageActivator() : this(null)
   5:     {}
   6:  
   7:     public DefaultViewPageActivator(IDependencyResolver resolver)
   8:     {
   9:         Func<IDependencyResolver> func = null;
  10:         if (resolver == null)
  11:         {
  12:             this._resolverThunk = () => DependencyResolver.Current;
  13:         }
  14:         else
  15:         {
  16:             if (func == null)
  17:             {
  18:                 func = () => resolver;
  19:             }
  20:             this._resolverThunk = func;
  21:         }
  22:     }
  23:  
  24:     public object Create(ControllerContext controllerContext, Type type)
  25:     {
  26:         return (this._resolverThunk().GetService(type) ?? Activator.CreateInstance(type));
  27:     }
  28: }

如果我們在構造BuildManagerCompiledView的時候沒有指定具體的ViewPageActivator,那麼ASP.NET MVC會根據指定的DependencyResolver來創建默認的DefaultViewPageActivator。如果我們隻是根據ControllerContext和View文件虛擬路徑來構建BuildManagerCompiledView,最終用於激活WebPageView的實際上就是當前的DependencyResolver。換句話說,,接下來我們會演示相關的實例。

BuildManagerCompiledView對View的呈現機製其實很簡單。。BuildManagerCompiledView將利用激活的WebPageView對象呈現View的邏輯定義在抽象方法RenderView中,而Render方法僅僅實現了根據View文件虛擬路徑對WebPageView的激活,具體的實現可以通過如下的代碼片斷來體現。

   1: public abstract class BuildManagerCompiledView : IView
   2: {
   3:     //其他成員
   4:     public void Render(ViewContext viewContext, TextWriter writer)
   5:     {
   6:         Type viewType = BuildManager.GetCompiledType(ViewPath);
   7:         object instance = null;
   8:         if (null != viewType)
   9:         {
  10:             //controllerContext字段表示在構造函數中指定的ControllerContext
  11:             instance = this.ViewPageActivator.Create(controllerContext, viewType);
  12:         }
  13:         this.RenderView(viewContext, writer, instance);
  14:     }
  15:     protected abstract void RenderView(ViewContext viewContext, TextWriter writer, object instance);
  16: }

表示Razor引擎下的View的類型RazorView直接繼承BuildManagerCompiledView。如下麵的代碼片斷所示,它具有額外的三個隻讀屬性屬性。LayoutPath表示View使用的布局文件的虛擬路徑,而RunViewStartPages和ViewStartFileExtensions屬性與通過“_ViewStart.cshtml”或“_ViewStart.vbhtml”文件定義的開始頁麵有關,前者表示是否需要執行開始頁麵,後者表示開始頁麵文件的擴展名。對於Razor引擎默認創建的RazorView,RunViewStartPages屬性為True(意味著總是會執行開始頁麵)。ViewStartFileExtensions屬性表示的字符串集合包含兩個元素“cshtml”和“vbhtml”。

   1: public class RazorView : BuildManagerCompiledView
   2: {
   3:     public RazorView(ControllerContext controllerContext, string viewPath, string layoutPath, bool runViewStartPages, IEnumerable<string> viewStartFileExtensions);
   4:     public RazorView(ControllerContext controllerContext, string viewPath, string layoutPath, bool runViewStartPages, IEnumerable<string> viewStartFileExtensions, IViewPageActivator viewPageActivator);
   5:  
   6:     protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance);
   7:   
   8:     public string               LayoutPath { get; }
   9:     public bool                 RunViewStartPages { get; }  
  10:     public IEnumerable<string>  ViewStartFileExtensions { get; }
  11: }

RazorView通過實現RenderView方法最終完成了對View的呈現。方法傳入參數instance是通過BuildManagerCompiledView激活的View對象,通過上麵的介紹我們知道這是一個空的WebViewPage<TModel>對象(默認情況下是通過默認構造函數創建的)。RazorView在RenderView方法中對其進行初始後調用ExecutePageHierarchy方法將整個頁麵內容呈現出來。RazorView實現RenderView方法的邏輯基本上可以通過如下的代碼片斷來表示。

   1: public class RazorView : BuildManagerCompiledView
   2: {
   3:     //其他成員
   4:     protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance)
   5:     {       
   6:         WebViewPage page = instance as WebViewPage;
   7:         //初始化WebViewPage
   8:         Initialize(page);
   9:  
  10:         //得到表示開啟頁麵的WebPageRenderingBase對象
  11:         WebPageRenderingBase startPage;
  12:         if (this.RunViewStartPages)
  13:         {
  14:             startPage = StartPage.GetStartPage(page,"_ViewStart",this.ViewStartFileExtensions);
  15:         }
  16:         HttpContextBase httpContext = viewContext.HttpContext;
  17:         page.ExecutePageHierarchy(new WebPageContext(viewContext.HttpContext, null, null), writer, startPage);
  18:     }
  19: }

為了讓讀者了解RazorView實現 View呈現的本質,我們按照其實現原理自定義一個簡單的RazorView類型。我們在一個ASP.NET MVCWeb應用中定義了如下一個表示自定義RazorView的SimpleRazorView類型。SimpleRazorView直接實現了IView接口,在構造函數中初始化的屬性ViewPath表示View文件的虛擬路徑。

   1: public class SimpleRazorView: IView
   2: {
   3:     public string ViewPath { get; private set; }
   4:  
   5:     public SimpleRazorView(string viewPath)
   6:     {
   7:         this.ViewPath = viewPath;
   8:     }
   9:  
  10:     public void Render(ViewContext viewContext, TextWriter writer)
  11:     {
  12:         Type viewType    = BuildManager.GetCompiledType(this.ViewPath);
  13:         object instance  = Activator.CreateInstance(viewType);
  14:         WebViewPage page = (WebViewPage)instance as WebViewPage;
  15:  
  16:         page.VirtualPath     = this.ViewPath;
  17:         page.ViewContext     = viewContext;
  18:         page.ViewData        = viewContext.ViewData;
  19:         page.InitHelpers();
  20:  
  21:         WebPageContext pageContext     = new WebPageContext(viewContext.HttpContext, null, null);
  22:         WebPageRenderingBase startPage = StartPage.GetStartPage(page,"_ViewStart",new string[]{"cshtml","vbhtml"});
  23:         page.ExecutePageHierarchy(pageContext, writer, startPage);
  24:     }
  25: }

在用於呈現View的Render方法中,我們利用BuildManager根據當前View文件的虛擬路徑得到動態編譯後的類型,然後利用該類型以反射的方式創建一個WebViewPage對象。接下來我們初始化該WebViewPage對象的VirtualPath、VirewContext和ViewData屬性,並調用InitHelpers方法對HtmlHelper、UrlHelper和AjaxHelper進行初始化。

SimpleRazorView總是會執行開始頁麵,所以我們通過調用ViewStartPage的靜態方法GetStartPage根據指定的開始頁麵文件名(_ViewStart)和擴展名列表(cshtml和vbhtml)得到表示開始頁麵的WebPageRenderingBase對象。最後我們創建WebPageContext對象,並將它和表示開始頁麵的WebPageRenderingBase對象作為參數調用WebViewPage的ExecutePageHierarchy方法實現對整個頁麵的呈現。

為了驗證SimpleRazorView能夠正常完成對View內容的呈現,我們定義了如下一個HomeController。在默認的Action方法Index中,我們創建一個Contact對象作為當前ViewData的Model。然後通過指定View文件的虛擬路徑(“~/Views/Home/Index.cshtml”)創建我們自定義的SimpleRazorView對象。最後我們創建ViewContext,並將其作為參數調用SimpleRazorView的Render方法將默認的View呈現出來。

   1: public class HomeController : Controller
   2: {
   3:     public void Index()
   4:     {
   5:         ViewData.Model = new Contact { 
   6:             Name         = "張三", 
   7:             PhoneNo      = "123456789", 
   8:             EmailAddress = "zhangsan@gmail.com" };
   9:         SimpleRazorView view    = new SimpleRazorView("~/Views/Home/Index.cshtml");
  10:         ViewContext viewContext = new ViewContext(ControllerContext, view, ViewData, TempData, Response.Output);
  11:         view.Render(viewContext, viewContext.Writer);
  12:     }
  13: }
  14:  
  15: public class Contact
  16: {
  17:     [DisplayName("姓名")]
  18:     public string Name { get; set; }
  19:  
  20:     [DisplayName("電話號碼")]
  21:     public string PhoneNo { get; set; }
  22:  
  23:     [DisplayName("電子郵箱地址")]
  24:     public string EmailAddress { get; set; }
  25: }

我們的View很簡單,如下麵的代碼片斷所示,這是一個Model類型為Contact的強類型View,在該View中我們直接調用HtmlHelper<TModel>的擴展方法EditorForModel將作為Model的Contact對象以編輯模式呈現在一個表單之中。

   1: @model Contact
   2: @{
   3:     ViewBag.Title = Model.Name;
   4: }
   5:  
   6: @using (Html.BeginForm())
   7: { 
   8:     @Html.EditorForModel()
   9:     <input type="submit" value="保存" />
  10: }

為了驗證我們自定義的SimpleRazorView對布局文件和_ViewStart頁麵的支持,我們在“~/Views/Shared/”目錄下定義了如下一個名為“_Layout.cshtml”的布局文件。布局文件的設置通過定義在“~/Views/”目錄下具有如下定義的“_ViewStart.cshtml”文件來指定。

   1: _Layout.cshtml:
   2: <html>
   3:     <head>
   4:         <title>@ViewBag.Title </title>
   5:     </head>    
   6:     <body>
   7:         <h3>編輯聯係人信息</h3>
   8:         @RenderBody()
   9:     </body>
  10: </html>
  11:  
  12: _ViewStart.cshtml:
  13: @{
  14:     Layout = "~/Views/Shared/_Layout.cshtml";
  15: }

運行我們的程序後直接會在瀏覽器中呈現如下圖所示的效果,可以看出這和我們直接在Action方法Index方法返回一個ViewResult對象沒有本質的區別。

image

ASP.NET MVC的Razor引擎:View編譯原理
ASP.NET MVC的Razor引擎:RazorView
ASP.NET MVC的Razor引擎:IoC在View激活過程中的應用
ASP.NET MVC的Razor引擎:RazorViewEngine


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-25 16:33:54

  上一篇:go  ASP.NET MVC的Razor引擎:View編譯原理
  下一篇:go  ASP.NET MVC的Razor引擎:IoC在View激活過程中的應用