Agenda
前言 繼承ActiontResult類別中ViewResultBase最為複雜,因為ViewResultBase要找到實現IViewEngine物件取得取得View檔案,在透過實現IView物件把頁面渲染出來.
這篇會跟大家分享值型上面動作核心類別.
個人覺得MVC 運用很多物件導向概念和用法,在讀程式時有件事情很重要是理解類別負責的工作和類別之間關係.就像現實生活中人與人的關係要了解清楚.
我有做一個可以針對於Asp.net MVC Debugger 的專案,只要下中斷點就可輕易進入Asp.net MVC原始碼.
ViewResultBase.ExecuteResult 因為ExecuteResult是最終被呼叫方法,我們來解析ViewResultBase.ExecuteResult方法邏輯.
透過子類別實現FindView取得View相關資料.
呼叫實現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()方法就是建立PartialViewResult和ViewResult方法並且呼叫ExecuteResult進行View頁面渲染.
IView View是一個實現了IView介面物件。IView定義非常簡單,僅僅具有唯一Render方法根據指定ViewContext和TextWriter物件達成對於View渲染顯示
1 2 3 4 public interface IView { void Render (ViewContext viewContext, TextWriter writer ) ; }
BuildManagerCompiledView BuildManagerCompiledView類別實現Render對於View如何被渲染呈現.
主要透過下面幾個步驟.
.cshtml,.aspx頁面程式碼會轉成編譯成一個繼承WebViewPage類別的dll檔案.BuildManagerWrapper靜態方法GetCompiledType依據指定View檔案虛擬路徑得到編譯後WebPageView類型
IViewPageActivator(DefaultViewPageActivator)利用反射建立WebPageView物件由頁面程式產生的View物件
最後再呼叫由子類實現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 ; 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具有三個只讀屬性
LayoutPath:View佈局檔案虛擬路徑
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方法執行幾個步驟.
RenderView方法將BuildManagerCompiledView方法取得instance物件轉換型別成WebViewPage
資料初始化(建立UrlHelp,….)物件
判斷是否使用Razor共用樣板
呼叫ExecutePageHierarchy,進行頁面渲染,最主要呼叫Execute方法來執行子類別實現邏輯.
下面是IView類別關係圖
最後由WebFormView,RazorView實現頁面的渲染工作.
IViewEngine 這個介面提供找尋使用ViewEngineResult,View和ViewEngine屬性找到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這個抽象類別,實現FindPartialView和FindView方法,另外提供一個抽象方法CreateView和CreatePartialView提供子類(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就是呼叫IView的Render方法.
RazorViewEngine就是建立到時候要Render到OutputStream物件.
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類別關係圖如下
這邊以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有支援兩個副檔名
vbhtml:vb使用
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 許可協議。轉載請註明出處!