550
windows
ASP.NET MVC的View是如何被呈現出來的?[設計篇]
在前麵的四篇文章中,我們介紹了各種ActionResult以及相關的請求響應機製,但是與“View的呈現”相關的ActionResult是ViewResult。通過ViewResult的執行實現的對View的呈現比上麵我們介紹的各種ActionResult要複雜得多,ASP.NET MVC內部設計了一個擴展的View引擎實現了最終的View呈現工作。[本文已經同步到《How ASP.NET MVC Works?》中]
目錄
一、View引擎中的View
二、ViewEngine
三、ViewResult的執行
ASP.NET MVC為我們提供了兩種View引擎,它們針對不同的動態View設計方式。一種是傳統的Web Form引擎,由於該引擎下View的設計與我們定義.aspx頁麵一致,又稱為ASPX引擎。另外一種則是本書默認采用同時也是推薦使用的Razor引擎。在兩種View引擎的工作機製之前,有一個必須要知道的問題:View如何表示?提到View,很多ASP.NET MVC的開發人員可能首先想到的就是定義UI界麵的.aspx文件(Web Form引擎)或者.cshtml/.vbhtml文件(Razor引擎)。其實對於View引擎來說,View是一個實現了IView接口類型的對象。如下麵的代碼片斷所示,IView的定義非常簡單,僅僅具有唯一的Render方法根據指定的View上下文和TextWriter對象實現對View的呈現。
1: public interface IView
2: {
3: void Render(ViewContext viewContext, TextWriter writer);
4: }
5:
6: public class ViewContext : ControllerContext
7: {
8: //其他成員
9: public virtual bool ClientValidationEnabled { get; set; }
10: public virtual bool UnobtrusiveJavaScriptEnabled { get; set; }
11:
12: public virtual TempDataDictionary TempData { get; set; }
13: [Dynamic]
14: public object ViewBag { [return: Dynamic] get; }
15: public virtual ViewDataDictionary ViewData { get; set; }
16: public virtual IView View { get; set; }
17: public virtual TextWriter Writer { get; set; }
18: }
19:
20: public abstract class HttpResponseBase
21: {
22: //其他成員
23: public virtual TextWriter Output { get; set; }
24: }
IView用於呈現View的Render方法具有兩個參數,一個是表示View上下文的ViewContext對象。通過上麵的代碼片斷可以看出ViewContext是ControllerContext的子類,用於表示狀態數據的ViewData、ViewBag和TempData對應著ControllerBase的同名屬性。也就是說當執行從Controller的某個Action方法返回的ViewResult的時候,通過創建的ViewContext保持的狀態數據直接來源於Controller對象。
ViewContext具有兩個布爾類型屬性ClientValidationEnabled和UnobtrusiveJavaScriptEnabled表示是否支持客戶端驗證和Unobtrusive JavaScript。默認的情況下著兩個屬性通過同名的AppSettings配置項進行設置。如果應用不具有對應的配置,兩個屬性默認值為False。
1: <configuration>
2: <appSettings>
3: <add key="ClientValidationEnabled" value="true"/>
4: <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
5: </appSettings>
6: </configuration>
配置的範圍是針對整個Web應用而言的,這個全局屬性還可以通過HtmlHelper的同名靜態屬性進行設置。值得一提的是,ASP.NET MVC 允許我們針對某個View開啟或者關閉對客戶端驗證和UnobtrusiveJavaScriptEnabled的支持,而這可以通過當前View的HtmlHelper的實例方法EnableClientValidation/EnableUnobtrusiveJavaScript來實現。
1: public class HtmlHelper
2: {
3: //其他成員
4: public void EnableClientValidation();
5: public void EnableClientValidation(bool enabled);
6: public void EnableUnobtrusiveJavaScript();
7: public void EnableUnobtrusiveJavaScript(bool enabled);
8:
9: public static bool ClientValidationEnabled { get; set; }
10: public static bool UnobtrusiveJavaScriptEnabled { get; set; }
11: }
接口IView的Render方法的第二個參數是一個TextWriter對象。對於該方法來說,隻要我們將內容寫入該TextWriter即完成了針對相關內容在View上的呈現,因為在調用Render方法的時候,作為該參數的是當前。
View引擎的核心是一個ViewEngine對象,它實現了IViewEngine接口。如下麵的代碼片斷所示,IViewEngine定義了兩個FindView和FindPartialView方法根據指定的Controller上下文、View名稱和布局文件名稱去獲取對應的View和Partial View,兩個方法中具有一個布爾類型的參數useCache表示是否啟用緩存。另一個方法ReleaseView用於釋放View對象。
1: public interface IViewEngine
2: {
3: ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache);
4: ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache);
5: void ReleaseView(ControllerContext controllerContext, IView view);
6: }
FindView和FindPartialView方法返回的並不是實現了IView接口的類型的對象,而是一個類型為System.Web.Mvc.ViewEngineResult對象。如下麵的代碼片斷所示,ViewEngineResult的隻讀屬性View和ViewEngine屬性表示找到的View對象和表示自身的ViewEngine對象。在成功獲取到對應View的情況下這兩個屬性會通過構造函數進行初始化。如果沒有找到相應的View,則將一個搜尋位置列表傳入另一個構造函數創建一個ViewEngineResult,而隻讀屬性SearchedLocations表示的就是這麼一個搜尋位置列表。
1: public class ViewEngineResult
2: {
3: public ViewEngineResult(IEnumerable<string> searchedLocations);
4: public ViewEngineResult(IView view, IViewEngine viewEngine);
5:
6: public IEnumerable<string> SearchedLocations { get; }
7: public IView View { get; }
8: public IViewEngine ViewEngine { get; }
9: }
如果返回的ViewEngineResult包含一個具體的View,那麼這個View將會最終被呈現出來。反之,如果ViewEngineResult僅僅包含一個通過SearchedLocations屬性表示的在獲取目標View過程中使用的搜索位置列表,那麼最終呈現出來的就是如下圖所示的包含該列表的錯誤頁麵。
我們可以通過一個簡單的實例來驗證這一點。在通過Viual Studio的ASP.NET MVC項目模板創建的空Web應用中,我們定義了如下一個HomeController。在默認的Action方法Index中,我們通過ViewEngines的靜態隻讀屬性Engines得到一個全局ViewEngine列表,並調用其FindView方法試圖去尋找一個根本不存在View(“NonExistentView”)。最後我們將得到的ViewEngineResult對象的SearchedLocations屬性表示的搜尋位置列表呈現出來。
1: public class HomeController : Controller
2: {
3: public void Index()
4: {
5: ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext, "NonExistentView", null);
6: foreach (string location in result.SearchedLocations)
7: {
8: Response.Write(location + "<br/>");
9: }
10: }
11: }
運行我們的程序後表示在獲取目標View中采用的搜尋位置列表會如下圖所示的方式呈現出來,而這個列表與上圖是完全一致的。
在上麵實例演示中涉及到了一個重要的靜態類型ViewEngines,它通過如下定義的隻讀屬性Engines維護一個全局ViewEngine列表。從給出的定義可以看出,兩個原生的ViewEngine在初始化的時候就被添加到了該列表中,它們的類型就是分別代表Web Form和Razor引擎的WebFormViewEngine和RazorViewEngine如果我們創建了一個自定義View引擎,相應的ViewEngine也可以通過ViewEngines進行注冊。
1: public static class ViewEngines
2: {
3: private static readonly ViewEngineCollection _engines = new ViewEngineCollection { new WebFormViewEngine(), new RazorViewEngine() };
4:
5: public static ViewEngineCollection Engines
6: {
7: get { return _engines;}
8: }
9: }
10:
11: public class ViewEngineCollection : Collection<IViewEngine>
12: {
13: //其他成員
14: public virtual ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName);
15: public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName);
16: }
ViewEngines的靜態隻讀屬性Engines的類型是ViewEngineCollection,它是一個元素類型為IViewEngine的集合。ViewEngineCollection同樣定義了FindView/FindPartialView這兩個方法用於獲取指定名稱的View和分部View,在方法內部它會遍曆集合中 的ViewEngine對象並調用它們的同名方法直到找的一個具體的View或者Partial View。由於WebFormViewEngine排在RazorViewEngine之前,所以前者會被優先使用,這可以從上麵兩張截圖所示的搜尋位置列表看出來(先搜索.aspx和.ascx,再搜索.cshtml和.vbhtml)。
對於ViewEngineCollection的FindView/FindPartialView方法來說,不知道讀者是否注意到了它們沒有一個表示是否采用緩存的useCache參數。實際上當這兩個方法被調用的時候,會先采用緩存的方式調用相應的ViewEngine,如果返回為Null,則以不采用緩存的方式再次調用它們。
View引擎對View的獲取以及對View的呈現最初是通過ViewResult觸發的,那麼兩者是如何銜接的呢?這是本小節著重討論的問題,在這之前我們不妨先來看看ViewResult的定義。如下麵的代碼片斷所示,表示ViewResult的類型ViewResult是抽象類ViewResultBase的子類。
1: public class ViewResult : ViewResultBase
2: {
3: protected override ViewEngineResult FindView(ControllerContext context);
4: public string MasterName { get; set; }
5: }
6:
7: public abstract class ViewResultBase : ActionResult
8: {
9: public override void ExecuteResult(ControllerContext context);
10: protected abstract ViewEngineResult FindView(ControllerContext context);
11:
12: public object Model { get; }
13: public TempDataDictionary TempData { get; set; }
14: [Dynamic]
15: public object ViewBag { [return: Dynamic] get; }
16: public ViewDataDictionary ViewData { get; set; }
17: public string ViewName { get; set; }
18: public ViewEngineCollection ViewEngineCollection { get; set; }
19: public IView View { get; set; }
20: }
ViewResultBase的隻讀屬性Model表示作為View的Model對象,三個表示數據狀態的屬性(ViewData、ViewBag和TempData)來源於Controller的同名屬性。View和ViewName屬性則是代表具體的View對象和View的名稱。ViewEngineCollection屬性值默認來源於ViewEngines的靜態屬性Engines代表的全局ViewEngine列表。
ViewResultBase用於獲取具體View的FindView方法在ViewResult類中被實現,後者提供了額外的屬性MasterName表示布局文件名稱。在FindView方法的內部會直接調用ViewEngineCollection屬性的FindView方法,如果返回的ViewEngineResult包含一個具體的View(View屬性不為空),則直接返回該ViewEngineResult,否則拋出一個InvalidOperation異常,並將通過ViewEngineResult的SearchedLocations屬性表示的搜尋位置列表格式化成一個字符串作為該異常的消息,所以圖8-5所示的搜尋位置列表實際上是拋出的InvalidOperation異常的消息。
ASP.NET MVC的View引擎涉及到的相關的類型/接口以及它們之間的關係可以通過如圖下所示的UML來表示。ViewResult通過靜態類型ViewEngines利用View引擎激活對應的View對象並最終將View的內容呈現出來。
與除EmptyResult以外的所有ActionResult類型一樣,抽象類Conrtoller中提供了相應的方法輔助創建ViewResult。如下麵的代碼片斷所示,Controller具有如下一係列View方法幫助我們根據指定的View名稱、View對象、布局文件名稱和Model對象創建相應的ViewResult。
1: public abstract class Controller : ControllerBase, ...
2: {
3: //其他成員
4: protected ViewResult View();
5: protected ViewResult View(object model);
6: protected ViewResult View(string viewName);
7: protected ViewResult View(IView view);
8: protected ViewResult View(string viewName, object model);
9: protected ViewResult View(string viewName, string masterName);
10: protected virtual ViewResult View(IView view, object model);
11: protected virtual ViewResult View(string viewName, string masterName, object model);
12: }
ViewResult與View引擎的交互體現在用於執行執行ActionView的ExecuteResult上。如下麵的代碼片斷所示,如果View屬性為Null,會調用FindView方法得到一個用於封裝指定名稱(如果沒有執行則采用當前的Action名稱作為View名稱)的View的ViewEngineResult對象,並將其View屬性作為自身的View。然後創建View上下文,並將該上下文和當前HttpResponse的Output屬性代表的TextWriter對象作為參數調用View對象的Render方法實現對View的最終呈現。View呈現完成之後,通過ViewEngineResult得到對應的ViewEngine,並調用其Release對象對View進行回收操作。
1: public abstract class ViewResultBase : ActionResult
2: {
3: //其他成員
4: public override void ExecuteResult(ControllerContext context)
5: {
6: //其他操作
7: if (string.IsNullOrEmpty(this.ViewName))
8: {
9: this.ViewName = context.RouteData.GetRequiredString("action");
10: }
11: ViewEngineResult result = null;
12: if (this.View == null)
13: {
14: result = this.FindView(context);
15: this.View = result.View;
16: }
17: TextWriter output = context.HttpContext.Response.Output;
18: ViewContext viewContext = new ViewContext(context, this.View, this.ViewData, this.TempData, output);
19: this.View.Render(viewContext, output);
20: if (result != null)
21: {
22: result.ViewEngine.ReleaseView(context, this.View);
23: }
24: }
25: }
ViewResult為們提供了一種與View引擎交互的手段,其實在進行View的獲取和呈現的時候完全可以拋開ViewResult,直接利用View引擎來完成,如下兩種Action方法的定義是完全等效的。
1: //Action方法直接返回ViewResult
2: public class HomeController : Controller
3: {
4: public ActionResult Index()
5: {
6: return View();
7: }
8: }
9:
10: //Action方法直接調用View引擎
11: public class HomeController : Controller
12: {
13: public void Index()
14: {
15: string viewName = ControllerContext.RouteData.GetRequiredString("action");
16: ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext, viewName, null);
17: if (null == result.View)
18: {
19: throw new InvalidOperationException(FormatErrorMessage(viewName,result.SearchedLocations));
20: }
21: try
22: {
23: ViewContext viewContext = new ViewContext(ControllerContext, result.View, this.ViewData, this.TempData, Response.Output);
24: result.View.Render(viewContext, viewContext.Writer);
25: }
26: finally
27: {
28: result.ViewEngine.ReleaseView(ControllerContext, result.View);
29: }
30: }
31:
32: private string FormatErrorMessage(string viewName, IEnumerable<string> searchedLocations)
33: {
34: string format = "The view '{0}' or its master was not found or no view engine supports the searched locations. The following locations were searched:{1}";
35: StringBuilder builder = new StringBuilder();
36: foreach (string str in searchedLocations)
37: {
38: builder.AppendLine();
39: builder.Append(str);
40: }
41: return string.Format(CultureInfo.CurrentCulture, format, viewName, builder);
42: }
43: }
上麵我們僅僅介紹了ViewResult利用View引擎進行View的獲取和呈現,其實當我們調用HtmlHelper的擴展方法Partial將指定的Partial View的HTML呈現出來時,內部調用View引擎的方式與之類
ASP.NET MVC的View是如何被呈現出來的?[設計篇]
ASP.NET MVC的View是如何被呈現出來的?[實例篇]
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-25 16:34:01
上一篇:
阿裏雲支撐馬來西亞數字自由貿易區落地 幫助馬來西亞中小企業參與全球貿易
下一篇:
ASP.NET MVC的View是如何呈現出來的[實例篇]
man 輸出的幫助信息輸出到文件中
linux驅動開發--字符設備:自動創建設備節點
Spark SQL組件源碼分析
Tomcat5發布項目問題(1):jstl java.lang.NoClassDefFoundError javaxelValueExpression
江蘇金融網貸APP平台開發
c# 泛型類--Dictionary
tomcat下的日誌配置詳細說明
java中short s=s+1和s+=1的區別
org.apache.hadoop.hbase.MasterNotRunningException: Retried 7 times 異常的解決
互聯網業務安全之通用安全風險模型