iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 25
2
Software Development

從Asp.net框架角度進入Asp.net MVC原始碼系列 第 25

[Day25] 動態產生程式碼(WebViewPage) View是如何被建立(四)

前言

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

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

本篇會來解析View編譯原理

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

WebViewPage

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_decompile.PNG

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

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

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

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

public override void WriteLiteral(object value)
{
    Output.Write(value);
}

呼叫WebViewPage.ExecutePageHierarchy方法時機

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);

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方法).

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可以更方便使用.

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框架.


上一篇
[Day24] 探討ViewEngine機制 View是如何被建立(三)
下一篇
[Day26] 動手DIY改造 Asp.net MVC- Route解析機制
系列文
從Asp.net框架角度進入Asp.net MVC原始碼30

尚未有邦友留言

立即登入留言