🚫 Ad Blocker Detected

Please disable your AD blocker to continue using this site. Ads help us keep the content free! please press keyboard F5 to refresh page after disabled AD blocker

請關閉廣告攔截器以繼續使用本網站。廣告有助於我們保證內容免費。謝謝! 關閉後請按 F5 刷新頁面

0%

動態產生程式碼(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

_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 許可協議。轉載請註明出處!

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