繼承ActiontResult
類別中ViewResultBase
最為複雜,因為ViewResultBase
要找到實現IViewEngine
物件取得取得View
檔案,在透過實現IView
物件把頁面渲染出來.
這篇會跟大家分享值型上面動作核心類別.
個人覺得MVC運用很多物件導向概念和用法,在讀程式時有件事情很重要是理解類別負責的工作和類別之間關係.就像現實生活中人與人的關係要了解清楚.
我有做一個可以針對於Asp.net MVC Debugger的專案,只要下中斷點就可輕易進入Asp.net MVC原始碼.
因為ExecuteResult
是最終被呼叫方法,我們來解析ViewResultBase.ExecuteResult
方法邏輯.
FindView
取得View
相關資料.IView
物件Render
方法,並將渲染出來資料透過Response.Output
輸出到Client
端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
頁面渲染.
View是一個實現了IView
介面物件。IView
定義非常簡單,僅僅具有唯一Render
方法根據指定ViewContext
和TextWriter
物件達成對於View
渲染顯示
public interface IView
{
void Render(ViewContext viewContext, TextWriter writer);
}
BuildManagerCompiledView
類別實現Render
對於View
如何被渲染呈現.
主要透過下面幾個步驟.
.cshtml,.aspx
頁面程式碼會轉成編譯成一個繼承WebViewPage
類別的dll
檔案.BuildManagerWrapper
靜態方法GetCompiledType
依據指定View
檔案虛擬路徑得到編譯後WebPageView
類型IViewPageActivator(DefaultViewPageActivator)
利用反射建立WebPageView
物件由頁面程式產生的View
物件RenderView
方法
BuildManagerCompiledView
屬性ViewPath
表示的就是View
文件虛擬路徑.
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
繼承BuildManagerCompiledView
,RazorView
具有三個只讀屬性
LayoutPath
:View
佈局檔案虛擬路徑ViewStartFileExtensions
:表示開始頁面文件的擴展名,對於Razor
引擎默認創建RazorView
,通過_ViewStart.cshtml
檔案定義開始頁面相關資訊.RunViewStartPages
:這個bool
掌控執行開始頁面判斷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
實現頁面的渲染工作.
這個介面提供找尋使用ViewEngineResult
,View
和ViewEngine
屬性找到View
物件和使用的ViewEngine
物件,SearchedLocations
屬性表示在獲取目標搜索過程中使用的搜索位置列表
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
這個抽象類別,實現FindPartialView
和FindView
方法,另外提供一個抽象方法CreateView
和CreatePartialView
提供子類(WebFormViewEngine
,RazorViewEngine
)來實現.
下面是FindView
原始碼.
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);
}
前面有提到VirtualPathProviderViewEngine
提供一個抽象類別給子類來實現如何建立一個IView
物件.
RazorViewEngine
透過上面資訊建立一個RazorView
(此類別實現IView
介面),最終ViewBaseResult
就是呼叫IView
的Render
方法.
RazorViewEngine
就是建立到時候要Render
到OutputStream
物件.
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.Engines
可以取得目前可以使用View
引擎.
ASP.NET MVC為我們提供了兩種View
引擎(RazorViewEngine
,WebFormViewEngine
),
.aspx
頁面一致WebFormViewEngine
,RazorViewEngine
。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執行效率.
MVC藉由ViewEngineCollection
這個集合來判斷使用ViewEngine
,且它預設有兩個ViewEngines
提供給我們使用(RazorViewEngine
,WebFormViewEngine
)一般來說我們只使用一個ViewEngine
另一個就不會用到.
如果我們只使用RazorViewEngine
就可在Global.cs
上撰寫這段程式碼,主要是把不必要ViewEngine
移除只關注在我們使用ViewEngine
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new RazorViewEngine());
在Razor
有支援兩個副檔名
vbhtml
:vb使用cshtml
:c#使用如果我們想強制這個專案都使用C#的Razor撰寫view,可藉由幾個屬性來幫我們限制完成.
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
:透過虛擬路徑找到要執行頁面(透過一些機制).