🚫 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%

探討ViewEngine機制 View是如何被建立(三) (第24天)

Agenda

前言

繼承ActiontResult類別中ViewResultBase最為複雜,因為ViewResultBase要找到實現IViewEngine物件取得取得View檔案,在透過實現IView物件把頁面渲染出來.

這篇會跟大家分享值型上面動作核心類別.

個人覺得MVC運用很多物件導向概念和用法,在讀程式時有件事情很重要是理解類別負責的工作和類別之間關係.就像現實生活中人與人的關係要了解清楚.

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

ViewResultBase.ExecuteResult

因為ExecuteResult是最終被呼叫方法,我們來解析ViewResultBase.ExecuteResult方法邏輯.

  1. 透過子類別實現FindView取得View相關資料.
  2. 呼叫實現IView物件Render方法,並將渲染出來資料透過Response.Output輸出到Client
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
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (String.IsNullOrEmpty(ViewName))
{
ViewName = context.RouteData.GetRequiredString("action");
}

ViewEngineResult result = null;

if (View == null)
{
result = FindView(context);
View = result.View;
}

TextWriter writer = context.HttpContext.Response.Output;
ViewContext viewContext = new ViewContext(context, View, ViewData, TempData, writer);
View.Render(viewContext, writer);

if (result != null)
{
result.ViewEngine.ReleaseView(context, View);
}
}

protected abstract ViewEngineResult FindView(ControllerContext context);

這張UML表示ViewResultBase繼承關係圖.

我們在Controller呼叫的View()PartailView()方法就是建立PartialViewResultViewResult方法並且呼叫ExecuteResult進行View頁面渲染.

UML_Model

IView

View是一個實現了IView介面物件。IView定義非常簡單,僅僅具有唯一Render方法根據指定ViewContextTextWriter物件達成對於View渲染顯示

1
2
3
4
public interface IView
{
void Render(ViewContext viewContext, TextWriter writer);
}

BuildManagerCompiledView

BuildManagerCompiledView類別實現Render對於View如何被渲染呈現.

主要透過下面幾個步驟.

  1. .cshtml,.aspx頁面程式碼會轉成編譯成一個繼承WebViewPage類別的dll檔案.BuildManagerWrapper靜態方法GetCompiledType依據指定View檔案虛擬路徑得到編譯後WebPageView類型
  2. IViewPageActivator(DefaultViewPageActivator)利用反射建立WebPageView物件由頁面程式產生的View物件
  3. 最後再呼叫由子類實現RenderView方法

BuildManagerCompiledView屬性ViewPath表示的就是View文件虛擬路徑.

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public abstract class BuildManagerCompiledView : IView
{
internal IViewPageActivator ViewPageActivator;
private IBuildManager _buildManager;
private ControllerContext _controllerContext;

internal IBuildManager BuildManager
{
get
{
if (_buildManager == null)
{
_buildManager = new BuildManagerWrapper();
}
return _buildManager;
}
set { _buildManager = value; }
}

public string ViewPath { get; protected set; }

public virtual void Render(ViewContext viewContext, TextWriter writer)
{
if (viewContext == null)
{
throw new ArgumentNullException("viewContext");
}

object instance = null;
//取得view型態
Type type = BuildManager.GetCompiledType(ViewPath);
if (type != null)
{
instance = ViewPageActivator.Create(_controllerContext, type);
}

if (instance == null)
{
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentCulture,
MvcResources.CshtmlView_ViewCouldNotBeCreated,
ViewPath));
}

RenderView(viewContext, writer, instance);
}

protected abstract void RenderView(ViewContext viewContext, TextWriter writer, object instance);
}

RazorView

RazorView繼承BuildManagerCompiledView,RazorView具有三個只讀屬性

  • LayoutPathView佈局檔案虛擬路徑
  • ViewStartFileExtensions:表示開始頁面文件的擴展名,對於Razor引擎默認創建RazorView,通過_ViewStart.cshtml檔案定義開始頁面相關資訊.
  • RunViewStartPages:這個bool掌控執行開始頁面判斷
  • 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
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    public class RazorView : BuildManagerCompiledView
    {
    public string LayoutPath { get; private set; }

    public bool RunViewStartPages { get; private set; }

    public IEnumerable<string> ViewStartFileExtensions { get; private set; }

    protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance)
    {
    if (writer == null)
    {
    throw new ArgumentNullException("writer");
    }

    WebViewPage webViewPage = instance as WebViewPage;
    if (webViewPage == null)
    {
    throw new InvalidOperationException(
    String.Format(
    CultureInfo.CurrentCulture,
    MvcResources.CshtmlView_WrongViewBase,
    ViewPath));
    }

    webViewPage.OverridenLayoutPath = LayoutPath;
    webViewPage.VirtualPath = ViewPath;
    webViewPage.ViewContext = viewContext;
    webViewPage.ViewData = viewContext.ViewData;

    webViewPage.InitHelpers();

    if (VirtualPathFactory != null)
    {
    webViewPage.VirtualPathFactory = VirtualPathFactory;
    }
    if (DisplayModeProvider != null)
    {
    webViewPage.DisplayModeProvider = DisplayModeProvider;
    }

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

RenderView方法執行幾個步驟.

  1. RenderView方法將BuildManagerCompiledView方法取得instance物件轉換型別成WebViewPage
  2. 資料初始化(建立UrlHelp,….)物件
  3. 判斷是否使用Razor共用樣板
  4. 呼叫ExecutePageHierarchy,進行頁面渲染,最主要呼叫Execute方法來執行子類別實現邏輯.

下面是IView類別關係圖

UML_Model

最後由WebFormView,RazorView實現頁面的渲染工作.

IViewEngine

這個介面提供找尋使用ViewEngineResult,ViewViewEngine屬性找到View物件和使用的ViewEngine物件,SearchedLocations屬性表示在獲取目標搜索過程中使用的搜索位置列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface IViewEngine
{
ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache);
ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache);
void ReleaseView(ControllerContext controllerContext, IView view);
}

public class ViewEngineResult
{
public IEnumerable<string> SearchedLocations { get; private set; }

public IView View { get; private set; }

public IViewEngine ViewEngine { get; private set; }
}

VirtualPathProviderViewEngine

VirtualPathProviderViewEngine這個抽象類別,實現FindPartialViewFindView方法,另外提供一個抽象方法CreateViewCreatePartialView提供子類(WebFormViewEngine,RazorViewEngine)來實現.

下面是FindView原始碼.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
//....

string[] viewLocationsSearched;
string[] masterLocationsSearched;

string controllerName = controllerContext.RouteData.GetRequiredString("controller");
string viewPath = GetPath(controllerContext, ViewLocationFormats, AreaViewLocationFormats, "ViewLocationFormats", viewName, controllerName, CacheKeyPrefixView, useCache, out viewLocationsSearched);
string masterPath = GetPath(controllerContext, MasterLocationFormats, AreaMasterLocationFormats, "MasterLocationFormats", masterName, controllerName, CacheKeyPrefixMaster, useCache, out masterLocationsSearched);

if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName)))
{
return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched));
}

return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
}

RazorViewEngine

前面有提到VirtualPathProviderViewEngine提供一個抽象類別給子類來實現如何建立一個IView物件.

RazorViewEngine透過上面資訊建立一個RazorView(此類別實現IView介面),最終ViewBaseResult就是呼叫IViewRender方法.

RazorViewEngine就是建立到時候要RenderOutputStream物件.

1
2
3
4
5
6
7
8
9
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
var view = new RazorView(controllerContext, viewPath,
layoutPath: masterPath, runViewStartPages: true, viewStartFileExtensions: FileExtensions, viewPageActivator: ViewPageActivator)
{
DisplayModeProvider = DisplayModeProvider
};
return view;
}

ViewEngines and ViewEngineCollection

透過ViewEngines.Engines可以取得目前可以使用View引擎.

ASP.NET MVC為我們提供了兩種View引擎(RazorViewEngine,WebFormViewEngine),

  • 提供傳統Web Form引擎,.aspx頁面一致WebFormViewEngine
  • 另一種預設使用也是推薦使用Razor引擎RazorViewEngine
1
2
3
4
5
6
7
8
9
10
11
12
13
public static class ViewEngines
{
private static readonly ViewEngineCollection _engines = new ViewEngineCollection
{
new WebFormViewEngine(),
new RazorViewEngine(),
};

public static ViewEngineCollection Engines
{
get { return _engines; }
}
}

ViewEngine類別關係圖如下

UML_Model

這邊以RazorViewEngine來介紹

  • ViewLocationFormats:預設找尋View實體檔案位置
  • PartialViewLocationFormats:預設找尋PartialView實體檔案位置
  • FileExtensions:Razor使用附檔名.

提升執行效率小技巧

這裡有個小技巧可提高MVC執行效率.

移除不必要ViewEngine提升執行效率

MVC藉由ViewEngineCollection這個集合來判斷使用ViewEngine,且它預設有兩個ViewEngines提供給我們使用(RazorViewEngine,WebFormViewEngine)一般來說我們只使用一個ViewEngine另一個就不會用到.

如果我們只使用RazorViewEngine就可在Global.cs上撰寫這段程式碼,主要是把不必要ViewEngine移除只關注在我們使用ViewEngine

1
2
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new RazorViewEngine());

只允許某個View副檔名

Razor有支援兩個副檔名

  1. vbhtml:vb使用
  2. cshtml:c#使用

如果我們想強制這個專案都使用C#的Razor撰寫view,可藉由幾個屬性來幫我們限制完成.

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
34
35
36
37
38
ViewEngines.Engines.Add(new RazorViewEngine()
{
AreaViewLocationFormats = new[]
{
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml",
},
AreaMasterLocationFormats = new[]
{
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml",
},
AreaPartialViewLocationFormats = new[]
{
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml"
},

ViewLocationFormats = new[]
{
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.cshtml",
},
MasterLocationFormats = new[]
{
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.cshtml"
},
PartialViewLocationFormats = new[]
{
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.cshtml"
},
FileExtensions = new[]
{
"cshtml",
}
});

小結:

本篇大致上把產生View頁面使用到的幾個核心介面和類別介紹完了,我們主要會使用繼承ViewResultBase物件並透過,相對應實現IView物件來進行畫面渲染,如何取得使用的IView物件就透過ViewEngines集合.

上面介紹了三個抽象類別和介面,每個都有自己核心職責並且和其他物件有清晰關係

  • ViewResultBase:實現ActionResult提供Controller呼叫產生頁面ExecuteResult方法.
  • IView:提供如何渲染頁面
  • IViewEngine:透過虛擬路徑找到要執行頁面(透過一些機制).

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

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