基於Web Form引擎的WebFormViewEngine和針對Razor引擎的RazorViewEngine都是抽象類型BuildManagerViewEngine的子類,而後者又繼承自VirtualPathProviderViewEngine。在這裡我們僅僅對實現在RazorViewEngine中View獲取的邏輯進行簡單介紹。由於Razor引擎下的View通過RazorView對象來表示,而RazorView通過View文件的虛擬路徑來構建,所以RazorViewEngine的View獲取機制在於根據當前上下文找到與指定View名稱相匹配的View文件(.cshtml或者.vbhtml文件),然後根據該 View文件的虛擬路徑創建一個RazorView對象並最終封裝成ViewEngineResult對象返回。
實現在RazorViewEngine中的目標View文件的搜索是根據一個預定義順序進行的。如果當前請求不是針對某個Area的,下面的列表代表了View的搜索順序:
~/Views/{ControllerName}/{ViewName}.cshtml
~/Views/{ControllerName}/{ViewName}.vbhtml
~/Views/Shared/{ViewName}.cshtml
~/Views/Shared/{ViewName}.vbhtml
對於針對某個Area的請求,RazorViewEngine會先按照如下的順序對目標View進行搜索。如果在這個列表中沒有成功找到目標View文件,會繼續按照上面的屬性進行搜索。
~/Areas/{AreaName}/Views/{ControllerName}/{ViewName}.cshtml
~/Areas/{AreaName}/Views/{ControllerName}/{ViewName}.vbhtml
~/Areas/{AreaName}/Views/ Shared /{ViewName}.cshtml
~/Areas/{AreaName}/Views/ Shared /{ViewName}.vbhtml
如果按照上面的搜索順序依然找不目標View文件,RazorViewEngine會根據這個列表創建並返回一個ViewEngineResult對象。這裡介紹的View搜索機制不僅僅應用於普通的View文件,還應用於Partial View和布局文件的搜索。
ViewEngine不僅僅通過FindView/FindPartialView根據當前上下文獲取指定的View,還通過ReleaseView對指定的View進行釋放回收操作。ReleaseView方法在RazorViewEngine的實現很簡單,如果指定的View對象的類型實現IDispose接口,它會直接調用其Dispose方法。下圖所示的UML體現了Razor引擎涉及的相關類型/接口以及它們之間的相互關系。
在《ASP.NET MVC的Razor引擎:RazorView》一文中我們創建了一個用於模擬RazorView的SimpleRazorView,現在我們為它創建一個對應的RazorViewEngine,我們直接在該實例項目中添加如下一個SimpleRazorViewEngine。
1: public class SimpleRazorViewEngine: IViewEngine
2: {
3: private string[] viewLocationFormats = new string[] {
4: "~/Views/{1}/{0}.cshtml",
5: "~/Views/{1}/{0}.vbhtml",
6: "~/Views/Shared/{0}.cshtml",
7: "~/Views/Shared/{0}.vbhtml" };
8: private string[] areaViewLocationFormats = new string[] {
9: "~/Areas/{2}/Views/{1}/{0}.cshtml",
10: "~/Areas/{2}/Views/{1}/{0}.vbhtml",
11: "~/Areas/{2}/Views/Shared/{0}.cshtml",
12: "~/Areas/{2}/Views/Shared/{0}.vbhtml" };
13:
14: public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
15: {
16: return FindView(controllerContext, partialViewName, null, useCache);
17: }
18:
19: public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
20: {
21: string controllerName = controllerContext.RouteData.GetRequiredString("controller");
22: object areaName;
23: List<string> viewLocations = new List<string>();
24: Array.ForEach(viewLocationFormats, format => viewLocations.Add(string.Format(format, viewName, controllerName)));
25: if (controllerContext.RouteData.Values.TryGetValue("area", out areaName))
26: {
27: Array.ForEach(areaViewLocationFormats, format=>viewLocations.Add(string.Format(format,viewName,controllerName, areaName)));
28: }
29: foreach (string viewLocation in viewLocations)
30: {
31: string filePath = controllerContext.HttpContext.Request.MapPath(viewLocation);
32: if (File.Exists(filePath))
33: {
34: return new ViewEngineResult(new SimpleRazorView(viewLocation),
35: this);
36: }
37: }
38: return new ViewEngineResult(viewLocations);
39: }
40:
41: public void ReleaseView(ControllerContext controllerContext, IView view)
42: {
43: IDisposable disposable = view as IDisposable;
44: if (null != disposable)
45: {
46: disposable.Dispose();
47: }
48: }
49: }