動態產生程式碼(WebViewPage) View是如何被建立(四) (第25天)

Agenda

前言

上一篇說到最終會透過一個實現IView物件(Razor是透過RazorView)來完成,RenderView方法將BuildManagerCompiledView方法取得物件轉換型別成WebViewPage.

.cshtml最終會編譯成一個繼承WebViewPage檔案.

本篇會來解析View編譯原理

我有做一個可以針對於Asp.net MVC Debugger的專案,只要下中斷點就可輕易進入Asp.net MVC原始碼.

WebViewPage

WebViewPage繼承樹最頂層有個WebPageExecutingBase抽象類別,他擁有一個抽象方法Execute,View轉成c#程式會建立一個類別就會繼承於WebViewPage並把使用者頁面程式碼實現在Execute方法.

1
public abstract void Execute();

先來看一下View產生的DLL檔案會放在哪裡

透過在View檔案上寫@GetType().Assembly.Location.

在頁面上顯示DLL存放位置,一般會放在Temp資料夾區中

可以根據顯示路徑找到View編譯成DLL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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_decompile.PNG

透過反編譯工具可以看到原始碼,每個頁面都會產生相對應的類別並繼承於WebViewPage<object>類別(會因為使用泛型,因為有一個@Model)

Page_Views_Home_About_cshtml類別命名有個規則.
Page_Views_{ViewfolderName}_{ViewFileName}_{ExtensionFileName}

我目前看到的是一個Aboutcshtml檔案(About.cshtml).

看到override void Execute()將我們頁面上的邏輯透過WriteLiteral將資料寫到Output上,在ApplicationStartPageWriteLiteral實作方式.

1
2
3
4
public override void WriteLiteral(object value)
{
Output.Write(value);
}

呼叫WebViewPage.ExecutePageHierarchy方法時機

RazorView類別中的RenderView方法最下面有一段程式碼.

先判斷是否取得StartPage在呼叫ExecutePageHierarchy方法進行頁面的渲染.

1
2
3
4
5
6
WebPageRenderingBase startPage = null;
if (RunViewStartPages)
{
startPage = StartPageLookup(webViewPage, RazorViewEngine.ViewStartFileName, ViewStartFileExtensions);
}
webViewPage.ExecutePageHierarchy(new WebPageContext(context: viewContext.HttpContext, page: null, model: null), writer, startPage);

ApplicationStartPage and WebPageRenderingBase

WebViewPage類別關係圖如下,WebViewPage擁有個複雜繼承樹.

WebViewPage_UML.png

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

https://docs.microsoft.com/zh-tw/aspnet/web-pages/overview/ui-layouts-and-themes/18-customizing-site-wide-behavior/_static/image1.jpg

  • ApplicationStartPage:當站台被啟動時會使用WebPageHttpModule(IHttpModule)初始化並呼叫ApplicationStartPageExecuteStartPageInternal找尋_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方法).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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);
}
}

WebViewPage vs WebViewPage

c# 有一個關鍵字new對於類別成員修飾詞

new關鍵字做為宣告修飾詞使用時,會明確隱藏繼承自基底類別的成員。當您隱藏繼承的成員時,該成員的衍生版本就會取代基底類別版本

new 修飾詞 (C# 參考)

我覺得這個關鍵字有點打壞物件導向的概念,因為他會把父類別原本的成員隱藏起來.強制替換成子類.

但我看到WebViewPage<TModel>實作時覺得new原來可以這麼好用

WebViewPage<TModel>很巧妙使用newView重點成員物件轉成泛型.可以讓我們在Razoraspx可以更方便使用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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>很巧妙使用newView重點成員物件轉成泛型.可以讓我們在Razoraspx可以更方便使用.

最後透過呼叫ActionResult.ExecuteResult方法將資料塞到Response物件中,提供回傳給Client端,最後執行資源Release動作.

後面幾篇會利用前面所學來改寫MVC框架.

此文作者:Daniel Shih(石頭)
此文地址https://isdaniel.github.io/ithelp-day25/
版權聲明:本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 TW 許可協議。轉載請註明出處!


如果本文對您幫助很大,可街口支付斗內鼓勵石頭^^