ASP.NET MVC的Razor引擎:RazorView
Razor引擎具有兩個(gè)核心的類型,一個(gè)是表示View本身的類型RazorView,另一個(gè)則是獲取和創(chuàng)建它的RazorViewEngine,我們將用兩篇文章對(duì)它們分別進(jìn)行剖析。Razor引擎下的View通過類型RazorView表示,它與表示W(wǎng)eb Form引擎View的類型WebFormView都是BuildManagerCompiledView的子類。[本文已經(jīng)同步到《How ASP.NET MVC Works?》中]
目錄
一、BuildManagerCompiledView
二、RazorView
三、實(shí)例演示:自定義View模擬RazorView的View呈現(xiàn)機(jī)制
一、BuildManagerCompiledView
為了能夠清楚地說明實(shí)現(xiàn)在BuildManagerCompiledView中的View激活與呈現(xiàn)機(jī)制,我們列出了BuildManagerCompiledView中與此相關(guān)的內(nèi)部和受保護(hù)的成員。
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)最終都會(huì)編譯成一個(gè)WebViewPage類型,所以通過RazorView/WebFormView體現(xiàn)的View的呈現(xiàn)機(jī)制最終體現(xiàn)在對(duì)WebViewPage對(duì)象的激活。我們可以利用BuildManager根據(jù)View文件的虛擬路徑得到編譯后的類型。從名稱也可以看出來,BuildManagerCompiledView內(nèi)部就是利用了BuildManager根據(jù)指定的View文件虛擬路徑完成對(duì)WebViewPage對(duì)象激活。
BuildManagerCompiledView的屬性ViewPath表示的就是View文件的虛擬路徑,該屬性在構(gòu)造函數(shù)中被初始化。BuildManagerCompiledView具有三個(gè)構(gòu)造函數(shù),對(duì)象本身的構(gòu)造邏輯體現(xiàn)在內(nèi)部構(gòu)造函數(shù)上。如上面的代碼片斷所示,除了將當(dāng)前ControllerContext和View文件虛擬路徑作為構(gòu)造函數(shù)的參數(shù)之外,該構(gòu)造函數(shù)還具有額外兩個(gè)參數(shù),其類型分別是IViewPageActivator和IDependencyResolver。
1: public interface IViewPageActivator
2: {
3: object Create(ControllerContext controllerContext, Type type);
4: }
上面的代碼片斷體現(xiàn)了接口IViewPageActivator的定義。顧名思義,該接口旨在實(shí)現(xiàn)對(duì)WebViewPage對(duì)象的激活,基于類型的對(duì)象激活機(jī)制實(shí)現(xiàn)在Create方法中。BuildManagerCompiledView的構(gòu)造函數(shù)中指定的ViewPageActivator被用于初始化內(nèi)部字段ViewPageActivator,如果沒有通過構(gòu)造函數(shù)顯式指定ViewPageActivator對(duì)象,默認(rèn)采用的是一個(gè)DefaultViewPageActivator對(duì)象。
DefaultViewPageActivator是一個(gè)具有如下定義的內(nèi)部類型,我們可以看到它實(shí)際上依賴于一個(gè)DependencyResolver對(duì)象完成針對(duì)WebViewPage對(duì)象的激活。這個(gè)DependencyResolver對(duì)象可以通過構(gòu)造函數(shù)進(jìn)行顯式設(shè)置,而默認(rèn)使用的DependencyResolver對(duì)象來源于DependencyResolver類型的靜態(tài)屬性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: }
如果我們?cè)跇?gòu)造BuildManagerCompiledView的時(shí)候沒有指定具體的ViewPageActivator,那么ASP.NET MVC會(huì)根據(jù)指定的DependencyResolver來創(chuàng)建默認(rèn)的DefaultViewPageActivator。如果我們只是根據(jù)ControllerContext和View文件虛擬路徑來構(gòu)建BuildManagerCompiledView,最終用于激活WebPageView的實(shí)際上就是當(dāng)前的DependencyResolver。換句話說,我們可以通過注冊(cè)自定義DependencyResolver的方法以IoC的方式來實(shí)現(xiàn)對(duì)WebPageView的激活,接下來我們會(huì)演示相關(guān)的實(shí)例。
BuildManagerCompiledView對(duì)View的呈現(xiàn)機(jī)制其實(shí)很簡(jiǎn)單。它調(diào)用BuildManager的靜態(tài)方法GetCompiledType根據(jù)指定的View文件虛擬路徑得到編譯后的WebPageView類型,然后將該類型交給ViewPageActivator激活一個(gè)具體的WebPageView對(duì)象,并調(diào)用其Render方法完成對(duì)View的最終呈現(xiàn)。BuildManagerCompiledView將利用激活的WebPageView對(duì)象呈現(xiàn)View的邏輯定義在抽象方法RenderView中,而Render方法僅僅實(shí)現(xiàn)了根據(jù)View文件虛擬路徑對(duì)WebPageView的激活,具體的實(shí)現(xiàn)可以通過如下的代碼片斷來體現(xiàn)。
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字段表示在構(gòu)造函數(shù)中指定的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: }
二、RazorView
表示Razor引擎下的View的類型RazorView直接繼承BuildManagerCompiledView。如下面的代碼片斷所示,它具有額外的三個(gè)只讀屬性屬性。LayoutPath表示View使用的布局文件的虛擬路徑,而RunViewStartPages和ViewStartFileExtensions屬性與通過“_ViewStart.cshtml”或“_ViewStart.vbhtml”文件定義的開始頁面有關(guān),前者表示是否需要執(zhí)行開始頁面,后者表示開始頁面文件的擴(kuò)展名。對(duì)于Razor引擎默認(rèn)創(chuàng)建的RazorView,RunViewStartPages屬性為True(意味著總是會(huì)執(zhí)行開始頁面)。ViewStartFileExtensions屬性表示的字符串集合包含兩個(gè)元素“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通過實(shí)現(xiàn)RenderView方法最終完成了對(duì)View的呈現(xiàn)。方法傳入?yún)?shù)instance是通過BuildManagerCompiledView激活的View對(duì)象,通過上面的介紹我們知道這是一個(gè)空的WebViewPage<TModel>對(duì)象(默認(rèn)情況下是通過默認(rèn)構(gòu)造函數(shù)創(chuàng)建的)。RazorView在RenderView方法中對(duì)其進(jìn)行初始后調(diào)用ExecutePageHierarchy方法將整個(gè)頁面內(nèi)容呈現(xiàn)出來。RazorView實(shí)現(xiàn)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對(duì)象
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: }
三、實(shí)例演示:自定義View模擬RazorView的View呈現(xiàn)機(jī)制
為了讓讀者了解RazorView實(shí)現(xiàn) View呈現(xiàn)的本質(zhì),我們按照其實(shí)現(xiàn)原理自定義一個(gè)簡(jiǎn)單的RazorView類型。我們?cè)谝粋€(gè)ASP.NET MVCWeb應(yīng)用中定義了如下一個(gè)表示自定義RazorView的SimpleRazorView類型。SimpleRazorView直接實(shí)現(xiàn)了IView接口,在構(gòu)造函數(shù)中初始化的屬性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: }
在用于呈現(xiàn)View的Render方法中,我們利用BuildManager根據(jù)當(dāng)前View文件的虛擬路徑得到動(dòng)態(tài)編譯后的類型,然后利用該類型以反射的方式創(chuàng)建一個(gè)WebViewPage對(duì)象。接下來我們初始化該WebViewPage對(duì)象的VirtualPath、VirewContext和ViewData屬性,并調(diào)用InitHelpers方法對(duì)HtmlHelper、UrlHelper和AjaxHelper進(jìn)行初始化。
SimpleRazorView總是會(huì)執(zhí)行開始頁面,所以我們通過調(diào)用ViewStartPage的靜態(tài)方法GetStartPage根據(jù)指定的開始頁面文件名(_ViewStart)和擴(kuò)展名列表(cshtml和vbhtml)得到表示開始頁面的WebPageRenderingBase對(duì)象。最后我們創(chuàng)建WebPageContext對(duì)象,并將它和表示開始頁面的WebPageRenderingBase對(duì)象作為參數(shù)調(diào)用WebViewPage的ExecutePageHierarchy方法實(shí)現(xiàn)對(duì)整個(gè)頁面的呈現(xiàn)。
為了驗(yàn)證SimpleRazorView能夠正常完成對(duì)View內(nèi)容的呈現(xiàn),我們定義了如下一個(gè)HomeController。在默認(rèn)的Action方法Index中,我們創(chuàng)建一個(gè)Contact對(duì)象作為當(dāng)前ViewData的Model。然后通過指定View文件的虛擬路徑(“~/Views/Home/Index.cshtml”)創(chuàng)建我們自定義的SimpleRazorView對(duì)象。最后我們創(chuàng)建ViewContext,并將其作為參數(shù)調(diào)用SimpleRazorView的Render方法將默認(rèn)的View呈現(xiàn)出來。
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("電話號(hào)碼")]
21: public string PhoneNo { get; set; }
22:
23: [DisplayName("電子郵箱地址")]
24: public string EmailAddress { get; set; }
25: }
我們的View很簡(jiǎn)單,如下面的代碼片斷所示,這是一個(gè)Model類型為Contact的強(qiáng)類型View,在該View中我們直接調(diào)用HtmlHelper<TModel>的擴(kuò)展方法EditorForModel將作為Model的Contact對(duì)象以編輯模式呈現(xiàn)在一個(gè)表單之中。
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: }
為了驗(yàn)證我們自定義的SimpleRazorView對(duì)布局文件和_ViewStart頁面的支持,我們?cè)凇皛/Views/Shared/”目錄下定義了如下一個(gè)名為“_Layout.cshtml”的布局文件。布局文件的設(shè)置通過定義在“~/Views/”目錄下具有如下定義的“_ViewStart.cshtml”文件來指定。
1: _Layout.cshtml:
2: <html>
3: <head>
4: <title>@ViewBag.Title </title>
5: </head>
6: <body>
7: <h3>編輯聯(lián)系人信息</h3>
8: @RenderBody()
9: </body>
10: </html>
11:
12: _ViewStart.cshtml:
13: @{
14: Layout = "~/Views/Shared/_Layout.cshtml";
15: }
運(yùn)行我們的程序后直接會(huì)在瀏覽器中呈現(xiàn)如下圖所示的效果,可以看出這和我們直接在Action方法Index方法返回一個(gè)ViewResult對(duì)象沒有本質(zhì)的區(qū)別。
ASP.NET MVC的Razor引擎:View編譯原理
ASP.NET MVC的Razor引擎:RazorView
ASP.NET MVC的Razor引擎:IoC在View激活過程中的應(yīng)用
ASP.NET MVC的Razor引擎:RazorViewEngine


Razor引擎具有兩個(gè)核心的類型,一個(gè)是表示View本身的類型RazorView,另一個(gè)則是獲取和創(chuàng)建它的WebFormViewEngine,我們將用兩篇文章對(duì)它們分別進(jìn)行剖析。Razor引擎下的View通過類型RazorView表示,它與表示W(wǎng)eb Form引擎View的類型WebFormView都是BuildManagerCompiledView的子類。

浙公網(wǎng)安備 33010602011771號(hào)