上一篇說到最終會透過一個實現IView物件(Razor是透過RazorView)來完成,RenderView方法將BuildManagerCompiledView方法取得物件轉換型別成WebViewPage.
.cshtml最終會編譯成一個繼承WebViewPage檔案.
本篇會來解析View編譯原理
我有做一個可以針對於Asp.net MVC Debugger的專案,只要下中斷點就可輕易進入Asp.net MVC原始碼.
WebViewPage繼承樹最頂層有個WebPageExecutingBase抽象類別,他擁有一個抽象方法Execute,View轉成c#程式會建立一個類別就會繼承於WebViewPage並把使用者頁面程式碼實現在Execute方法.
public abstract void Execute();
先來看一下View產生的DLL檔案會放在哪裡
透過在View檔案上寫@GetType().Assembly.Location.
在頁面上顯示DLL存放位置,一般會放在Temp資料夾區中
可以根據顯示路徑找到View編譯成DLL
public class _Page_Views_Shared__Layout_cshtml : WebViewPage<object>
{
protected global_asax ApplicationInstance
{
get
{
return (global_asax)this.get_Context().ApplicationInstance;
}
}
public override void Execute()
{
//... user print logic
}
}
我使用JustDecomplie反編譯工具,查看原始碼.
下圖對於View檔案產生DLL反編譯
透過反編譯工具可以看到原始碼,每個頁面都會產生相對應的類別並繼承於WebViewPage<object>類別(會因為使用泛型,因為有一個@Model)
Page_Views_Home_About_cshtml類別命名有個規則.Page_Views_{ViewfolderName}_{ViewFileName}_{ExtensionFileName}
我目前看到的是一個About的cshtml檔案(About.cshtml).
看到override void Execute()將我們頁面上的邏輯透過WriteLiteral將資料寫到Output上,在ApplicationStartPage有WriteLiteral實作方式.
public override void WriteLiteral(object value)
{
Output.Write(value);
}
在RazorView類別中的RenderView方法最下面有一段程式碼.
先判斷是否取得StartPage在呼叫ExecutePageHierarchy方法進行頁面的渲染.
WebPageRenderingBase startPage = null;
if (RunViewStartPages)
{
startPage = StartPageLookup(webViewPage, RazorViewEngine.ViewStartFileName, ViewStartFileExtensions);
}
webViewPage.ExecutePageHierarchy(new WebPageContext(context: viewContext.HttpContext, page: null, model: null), writer, startPage);
WebViewPage類別關係圖如下,WebViewPage擁有個複雜繼承樹.

主要分為兩派,在微軟官網有張圖來表示上面兩個比較

ApplicationStartPage:當站台被啟動時會使用WebPageHttpModule(IHttpModule)初始化並呼叫ApplicationStartPage的ExecuteStartPageInternal找尋_appstart.cshtml檔案來執行.ASP.NET Web Pages (Razor) 網站的自訂全網站行為
_AppStart.cshtml頁面上運作。 當要求傳入頁面中,和如果這是第一個要求任何頁面在網站中,ASP.NET會先檢查是否_AppStart.cshtml頁面存在。 如果是的話,任何程式碼中_AppStart.cshtml頁面上執行,並執行要求的頁面。
WebPageRenderingBase:透過ExecutePageHierarchy呼叫BaseLayout頁面或執行請求Execute方法WebPageBase類別中ExecutePageHierarchy重載實作,透過ExecutePageHierarchy呼叫開發者實現Execute方法(Page_Views_Home_About_cshtml.Execute方法).
public override void ExecutePageHierarchy()
{
if (WebPageHttpHandler.ShouldGenerateSourceHeader(Context))
{
try
{
string vp = VirtualPath;
if (vp != null)
{
string path = Context.Request.MapPath(vp);
if (!path.IsEmpty())
{
PageContext.SourceFiles.Add(path);
}
}
}
catch
{
// we really don't care if this ever fails, so we swallow all exceptions
}
}
TemplateStack.Push(Context, this);
try
{
// Execute the developer-written code of the WebPage
Execute();
}
finally
{
TemplateStack.Pop(Context);
}
}
c# 有一個關鍵字new對於類別成員修飾詞
new關鍵字做為宣告修飾詞使用時,會明確隱藏繼承自基底類別的成員。當您隱藏繼承的成員時,該成員的衍生版本就會取代基底類別版本
我覺得這個關鍵字有點打壞物件導向的概念,因為他會把父類別原本的成員隱藏起來.強制替換成子類.
但我看到WebViewPage<TModel>實作時覺得new原來可以這麼好用
WebViewPage<TModel>很巧妙使用new把View重點成員物件轉成泛型.可以讓我們在Razor或aspx可以更方便使用.
public abstract class WebViewPage<TModel> : WebViewPage
{
private ViewDataDictionary<TModel> _viewData;
public new AjaxHelper<TModel> Ajax { get; set; }
public new HtmlHelper<TModel> Html { get; set; }
public new TModel Model
{
get { return ViewData.Model; }
}
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This is the mechanism by which the ViewPage gets its ViewDataDictionary object.")]
public new ViewDataDictionary<TModel> ViewData
{
get
{
if (_viewData == null)
{
SetViewData(new ViewDataDictionary<TModel>());
}
return _viewData;
}
set { SetViewData(value); }
}
}
所以之後如果有遇到類似情況(需要使用泛型替代父類別object類型成員可以考慮使用new)
View頁面程式會轉成一個類別繼承於WebViewPage抽象類別,並把我們撰寫邏輯填充在Execute方法中.讓Asp.net MVC來呼叫.
這裡設計非常巧妙透過一個抽象類別和一個動態編譯程式,讓View更有彈性可以透過Razor語法實現View邏輯(更人性化).
WebViewPage<TModel>很巧妙使用new把View重點成員物件轉成泛型.可以讓我們在Razor或aspx可以更方便使用.
最後透過呼叫ActionResult.ExecuteResult方法將資料塞到Response物件中,提供回傳給Client端,最後執行資源Release動作.
後面幾篇會利用前面所學來改寫MVC框架.