本節將接着《白話學習MVC(九)View的呈現一》來繼續對ViewResult的詳細執行過程進行分析!html
九、ViewResultweb
ViewResult將視圖頁的內容響應給客戶端!
數組
因爲ViewResult的View呈現過程比較複雜,在此先大體描述一下整個流程:緩存
注:因爲對於ASP.NET MVC來講,xxx.cshtml文件稱爲視圖,而對於視圖引擎來講,實現IView接口的類稱爲視圖,爲了不混淆,有以下定義:
【視圖引擎】泛指那些實現了IViewEngine接口的類,如:WebFormViewEngine、RazorViewEngine。
【視圖】泛指那些實現了IView接口的類,如:WebViewPage、RazorView。
【視圖頁】例如:xxx.cshtml文件或xxx.aspx文件。
【視圖頁的名稱】指視圖頁的文件名。
mvc
對於ViewResult,它有一個父類ViewResultBase,其中定義着一些必要的屬性和ExecuteResult方法!
app
public abstract class ViewResultBase : ActionResult { private DynamicViewDataDictionary _dynamicViewData; private TempDataDictionary _tempData; private ViewDataDictionary _viewData; private ViewEngineCollection _viewEngineCollection; private string _viewName; public object Model { get { return ViewData.Model; } } public TempDataDictionary TempData { get { if (_tempData == null) { _tempData = new TempDataDictionary(); } return _tempData; } set { _tempData = value; } } public IView View { get; set; } public dynamic ViewBag { get { if (_dynamicViewData == null) { _dynamicViewData = new DynamicViewDataDictionary(() => ViewData); } return _dynamicViewData; } } public ViewDataDictionary ViewData { get { if (_viewData == null) { _viewData = new ViewDataDictionary(); } return _viewData; } set { _viewData = value; } } //獲取或設置視圖引擎,ASP.NET有兩個視圖引擎,分別是:WebFormViewEngine、RazorViewEngine。 public ViewEngineCollection ViewEngineCollection { get { return _viewEngineCollection ?? ViewEngines.Engines; } set { _viewEngineCollection = value; } } public string ViewName { get { return _viewName ?? String.Empty; } set { _viewName = value; } } public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } //若是沒有指定視圖頁名稱將當前Action做爲視圖頁的名稱 if (String.IsNullOrEmpty(ViewName)) { ViewName = context.RouteData.GetRequiredString("action"); } ViewEngineResult result = null; if (View == null) { //經過視圖引擎去建立視圖對象(實現了IView接口的類) 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); } }
public class ViewResult : ViewResultBase { private string _masterName; public string MasterName { get { return _masterName ?? String.Empty; } set { _masterName = value; } } protected override ViewEngineResult FindView(ControllerContext context) { //遍歷執行視圖引擎集合中每一個視圖引擎的FindView方法,若是檢查到存在指定的視圖頁(.cshtml),則建立視圖對象(實現IView接口),不然不建立視圖對象。 //此處ViewEngineCollection是ViewResultBase類中的一個屬性,表示視圖引擎集合。 ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName); //若是建立了視圖對象,即:指定路徑中存在相匹配的視圖頁(.cshtml文件)。 if (result.View != null) { return result; } //沒有建立視圖對象,即:指定路徑中不存在相匹配的視圖頁(.cshtml文件)。 StringBuilder locationsText = new StringBuilder(); foreach (string location in result.SearchedLocations) { locationsText.AppendLine(); locationsText.Append(location); } throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, MvcResources.Common_ViewNotFound, ViewName, locationsText)); } }
下面就來以ExecuteResult方法爲入口,並根據以上ViewResult及繼承的成員來逐步分析下執行流程:ide
① 當建立ViewResult對象時,若是沒有指定【視圖頁的名稱】,則從路由中獲取Action的名稱並做爲【視圖頁的名稱】。
函數
public abstract class ViewResultBase : ActionResult { public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } //若是沒有指定視圖頁名稱將當前Action做爲視圖頁的名稱 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); } } }
② 經過視圖引擎去建立視圖對象。(視圖對象用於處理視圖頁(.cshtml文件)的內容)
佈局
大體過程:經過視圖引擎,在指定的目錄下({controller}/{action})尋找與上一步中的【視圖頁的名稱】相匹配的視圖頁,若是能找到,則建立視圖對象(實現IView接口的類),該對象會在以後的步驟3時,執行Render方法來處理視圖頁的內容,例如:TempData、Html.xxx(...)等。不然,不建立視圖對象,而是將視圖引擎全部尋找的路徑經過異常輸出。學習
public abstract class ViewResultBase : ActionResult { public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } //若是沒有指定視圖頁的名稱將當前Action做爲視圖頁的名稱 if (String.IsNullOrEmpty(ViewName)) { ViewName = context.RouteData.GetRequiredString("action"); } ViewEngineResult result = null; if (View == null) { //經過視圖引擎去建立視圖對象,並將視圖對象和該視圖相關的信息封裝在ViewEngineResult對象中。 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); } } }
public class ViewResult : ViewResultBase { private string _masterName; public string MasterName { get { return _masterName ?? String.Empty; } set { _masterName = value; } } protected override ViewEngineResult FindView(ControllerContext context) { //遍歷執行視圖引擎集合中每一個視圖引擎的FindView方法,若是檢查到存在指定的視圖頁(.cshtml),則建立視圖對象(實現IView接口),不然不建立視圖對象。 //此處ViewEngineCollection是ViewResultBase類中的一個屬性,表示視圖引擎集合。 ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName); //若是建立了視圖對象,即:指定路徑中存在相匹配的視圖頁(.cshtml文件)。 if (result.View != null) { return result; } //沒有建立視圖對象,即:指定路徑中不存在相匹配的視圖頁(.cshtml文件)。 StringBuilder locationsText = new StringBuilder(); foreach (string location in result.SearchedLocations) { locationsText.AppendLine(); locationsText.Append(location); } throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, MvcResources.Common_ViewNotFound, ViewName, locationsText)); } }
建立視圖對象的任務實如今ViewResult了的FindView方法中,該方法的返回值類型爲ViewEngineResult!由此可知,ViewEngineCollection.FindView(context,ViewName,MasterName)就是建立視圖對象的切入點,此處的這個ViewEngineCollection實際上是ViewResultBase類中的一個屬性,該屬性的返回值是ViewEngineCollection類型的,表示視圖引擎集合,在沒有設置視圖引擎集合的狀況下,默認該視圖引擎集合中只有 ASPX引擎和Razor引擎。此處的這個FindView方法其實就是遍歷各個視圖引擎,並讓每一個視圖引擎均可以按照本身定義的方式去檢查是否存在指定的視圖頁,從而來決定是否建立視圖對象。上代碼!!!
public abstract class ViewResultBase : ActionResult { private ViewEngineCollection _viewEngineCollection; //獲取或設置視圖引擎集合,默認狀況下該集合兩個視圖引擎,分別是:WebFormViewEngine、RazorViewEngine。 public ViewEngineCollection ViewEngineCollection { get { return _viewEngineCollection ?? ViewEngines.Engines; } set { _viewEngineCollection = value; } } } public static class ViewEngines { //初始化Collection<T>,執行Collection<T>索引器將視圖引擎實例放入集合中 //同時執行ViewEngineCollection無參數的構造函數,將視圖引擎也保存在其變量中,用於以後執行FineView方法遍歷視圖引擎去檢查指定路徑中否存在想匹配的視圖頁(.cshtml) private static readonly ViewEngineCollection _engines = new ViewEngineCollection { new WebFormViewEngine(), new RazorViewEngine(), }; public static ViewEngineCollection Engines { get { return _engines; } } } public class ViewEngineCollection : Collection<IViewEngine> { private IResolver<IEnumerable<IViewEngine>> _serviceResolver; public ViewEngineCollection() { //將全部視圖引擎保存在變量_serviceResolver中,也是視圖引擎進行緩存的處理! _serviceResolver = new MultiServiceResolver<IViewEngine>(() => Items); } }
public class ViewEngineCollection : Collection<IViewEngine> { private IResolver<IEnumerable<IViewEngine>> _serviceResolver; public ViewEngineCollection() { //將全部視圖引擎保存在變量_serviceResolver中,也是視圖引擎進行緩存的處理! _serviceResolver = new MultiServiceResolver<IViewEngine>(() => Items); } public ViewEngineCollection(IList<IViewEngine> list) : base(list) { _serviceResolver = new MultiServiceResolver<IViewEngine>(() => Items); } internal ViewEngineCollection(IResolver<IEnumerable<IViewEngine>> serviceResolver, params IViewEngine[] engines) : base(engines) { _serviceResolver = serviceResolver ?? new MultiServiceResolver<IViewEngine>(() => Items); } //視圖引擎集合 private IEnumerable<IViewEngine> CombinedItems { get { return _serviceResolver.Current; } } protected override void InsertItem(int index, IViewEngine item) { if (item == null) { throw new ArgumentNullException("item"); } base.InsertItem(index, item); } protected override void SetItem(int index, IViewEngine item) { if (item == null) { throw new ArgumentNullException("item"); } base.SetItem(index, item); } public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName) { if (controllerContext == null) { throw new ArgumentNullException("controllerContext"); } if (String.IsNullOrEmpty(viewName)) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewName"); } //執行Find方法,該方法有兩個委託類型的參數。 //每一個委託類型都是以 視圖引擎 爲參數,並執行視圖引擎的FindView方法。 return Find(e => e.FindView(controllerContext, viewName, masterName, true), e => e.FindView(controllerContext, viewName, masterName, false)); } //參數是泛型委託 private ViewEngineResult Find(Func<IViewEngine, ViewEngineResult> cacheLocator, Func<IViewEngine, ViewEngineResult> locator) { return Find(cacheLocator, trackSearchedPaths: false) ?? Find(locator, trackSearchedPaths: true); } //trackSearchedPaths表示是否記錄尋找路徑 private ViewEngineResult Find(Func<IViewEngine, ViewEngineResult> lookup, bool trackSearchedPaths) { // Returns // 1st result // OR list of searched paths (if trackSearchedPaths == true) // OR null ViewEngineResult result; List<string> searched = null; if (trackSearchedPaths) { searched = new List<string>(); } //迭代視圖引擎,執行委託也就是執行每一個視圖引擎的FindView方法去檢查在指定的路徑中是否存在想匹配的視圖頁(.cshtml文件)。 //若是存在,則建立視圖對象(實現IView接口)。 foreach (IViewEngine engine in CombinedItems) { if (engine != null) { result = lookup(engine); if (result.View != null) { return result; } if (trackSearchedPaths) { searched.AddRange(result.SearchedLocations); } } } if (trackSearchedPaths) { // Remove duplicate search paths since multiple view engines could have potentially looked at the same path return new ViewEngineResult(searched.Distinct().ToList()); } else { return null; } } public virtual ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName) { if (controllerContext == null) { throw new ArgumentNullException("controllerContext"); } if (String.IsNullOrEmpty(partialViewName)) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "partialViewName"); } return Find(e => e.FindPartialView(controllerContext, partialViewName, true), e => e.FindPartialView(controllerContext, partialViewName, false)); } }
在FindView中,調用了參數爲兩個泛型委託 的Find方法。泛型委託的參數是IViewEngine(視圖引擎),其內容是實行當前參數(視圖引擎)的FindView方法,正式由於泛型委託,因此在以後遍歷視圖引擎集合中的每一個視圖引擎,並將視圖引擎做爲參數去執行這個泛型委託,從而實現調用每一個視圖引擎的FindView方法去尋找指定的視圖!上述<FindView方法的執行代碼>中能夠看出,最終實現 遍歷並執行泛型委託 落點在Find(Func<IViewEngine, ViewEngineResult> lookup, bool trackSearchedPaths)中。
//trackSearchedPaths表示是否記錄尋找路徑,從而實現當沒有尋找到View時,將全部的尋找路徑經過異常返回。 private ViewEngineResult Find(Func<IViewEngine, ViewEngineResult> lookup, bool trackSearchedPaths) { ViewEngineResult result; List<string> searched = null; if (trackSearchedPaths) { searched = new List<string>(); } //迭代視圖引擎,執行委託也就是執行每一個視圖引擎的FindView方法去檢查指定路徑是否存在相匹配的視圖頁,並在內部決定是否建立視圖對象! foreach (IViewEngine engine in CombinedItems) { if (engine != null) { result = lookup(engine);//重要:執行委託,調用視圖引擎的FindView方法! if (result.View != null) { return result; } if (trackSearchedPaths) { //檢查到沒有相匹配的視圖頁,那麼就將視圖引擎尋找的全部路徑添加到集合。 searched.AddRange(result.SearchedLocations); } } } if (trackSearchedPaths) { //將全部視圖引擎尋找的全部路徑返回, return new ViewEngineResult(searched.Distinct().ToList()); } else { return null; } }
上述代碼中,result=lookup(engine)就是執行視圖引擎的FindView方法,而默認狀況下的視圖引擎有:.ASPX引擎(WebFormViewEngine)、Razor引擎(RazorViewEngine),視圖引擎的FindView方法定義在其父類VirtualPathProviderViewEngine中,FindView方法內部會調用CreateView、CreatePartialView等方法,從而使不一樣的視圖引擎能按照本身的方式來進行檢查是否存在指定的視圖頁。對於視圖引擎,其相關的類和接口的關係以下圖:
下面是Razor引擎(RazorViewEngine)的詳細執行過程:
public abstract class VirtualPathProviderViewEngine : IViewEngine { // format is ":ViewCacheEntry:{cacheType}:{prefix}:{name}:{controllerName}:{areaName}:" private const string CacheKeyFormat = ":ViewCacheEntry:{0}:{1}:{2}:{3}:{4}:"; private const string CacheKeyPrefixMaster = "Master"; private const string CacheKeyPrefixPartial = "Partial"; private const string CacheKeyPrefixView = "View"; private static readonly string[] _emptyLocations = new string[0]; private DisplayModeProvider _displayModeProvider; private VirtualPathProvider _vpp; internal Func<string, string> GetExtensionThunk = VirtualPathUtility.GetExtension; protected VirtualPathProviderViewEngine() { if (HttpContext.Current == null || HttpContext.Current.IsDebuggingEnabled) { ViewLocationCache = DefaultViewLocationCache.Null; } else { ViewLocationCache = new DefaultViewLocationCache(); } } [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")] public string[] AreaMasterLocationFormats { get; set; } [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")] public string[] AreaPartialViewLocationFormats { get; set; } [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")] public string[] AreaViewLocationFormats { get; set; } [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")] public string[] FileExtensions { get; set; } [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")] public string[] MasterLocationFormats { get; set; } [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")] public string[] PartialViewLocationFormats { get; set; } public IViewLocationCache ViewLocationCache { get; set; } [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")] public string[] ViewLocationFormats { get; set; } protected VirtualPathProvider VirtualPathProvider { get { if (_vpp == null) { _vpp = HostingEnvironment.VirtualPathProvider; } return _vpp; } set { _vpp = value; } } protected internal DisplayModeProvider DisplayModeProvider { get { return _displayModeProvider ?? DisplayModeProvider.Instance; } set { _displayModeProvider = value; } } private string CreateCacheKey(string prefix, string name, string controllerName, string areaName) { return String.Format(CultureInfo.InvariantCulture, CacheKeyFormat, GetType().AssemblyQualifiedName, prefix, name, controllerName, areaName); } internal static string AppendDisplayModeToCacheKey(string cacheKey, string displayMode) { // key format is ":ViewCacheEntry:{cacheType}:{prefix}:{name}:{controllerName}:{areaName}:" // so append "{displayMode}:" to the key return cacheKey + displayMode + ":"; } protected abstract IView CreatePartialView(ControllerContext controllerContext, string partialPath); protected abstract IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath); protected virtual bool FileExists(ControllerContext controllerContext, string virtualPath) { return VirtualPathProvider.FileExists(virtualPath); } public virtual ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) { if (controllerContext == null) { throw new ArgumentNullException("controllerContext"); } if (String.IsNullOrEmpty(partialViewName)) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "partialViewName"); } string[] searched; string controllerName = controllerContext.RouteData.GetRequiredString("controller"); string partialPath = GetPath(controllerContext, PartialViewLocationFormats, AreaPartialViewLocationFormats, "PartialViewLocationFormats", partialViewName, controllerName, CacheKeyPrefixPartial, useCache, out searched); if (String.IsNullOrEmpty(partialPath)) { return new ViewEngineResult(searched); } return new ViewEngineResult(CreatePartialView(controllerContext, partialPath), this); } public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) { if (controllerContext == null) { throw new ArgumentNullException("controllerContext"); } if (String.IsNullOrEmpty(viewName)) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewName"); } string[] viewLocationsSearched; string[] masterLocationsSearched; string controllerName = controllerContext.RouteData.GetRequiredString("controller"); //根據定義的路徑去檢查是否存在想匹配的視圖頁(.cshtml)。 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); //檢查到不存在相匹配的視圖頁(.cshtml文件) if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName))) { //將檢查過的全部路徑封裝到ViewEngineResult對象中,以便以後經過異常的方式輸出。 return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched)); } //檢測存在相匹配的視圖頁,建立視圖對象(實現IView接口的類)。 return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this); } private string GetPath(ControllerContext controllerContext, string[] locations, string[] areaLocations, string locationsPropertyName, string name, string controllerName, string cacheKeyPrefix, bool useCache, out string[] searchedLocations) { searchedLocations = _emptyLocations; if (String.IsNullOrEmpty(name)) { return String.Empty; } string areaName = AreaHelpers.GetAreaName(controllerContext.RouteData); bool usingAreas = !String.IsNullOrEmpty(areaName); List<ViewLocation> viewLocations = GetViewLocations(locations, (usingAreas) ? areaLocations : null); if (viewLocations.Count == 0) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, MvcResources.Common_PropertyCannotBeNullOrEmpty, locationsPropertyName)); } bool nameRepresentsPath = IsSpecificPath(name); string cacheKey = CreateCacheKey(cacheKeyPrefix, name, (nameRepresentsPath) ? String.Empty : controllerName, areaName); if (useCache) { // Only look at cached display modes that can handle the context. IEnumerable<IDisplayMode> possibleDisplayModes = DisplayModeProvider.GetAvailableDisplayModesForContext(controllerContext.HttpContext, controllerContext.DisplayMode); foreach (IDisplayMode displayMode in possibleDisplayModes) { string cachedLocation = ViewLocationCache.GetViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, displayMode.DisplayModeId)); if (cachedLocation != null) { if (controllerContext.DisplayMode == null) { controllerContext.DisplayMode = displayMode; } return cachedLocation; } } // GetPath is called again without using the cache. return null; } else { return nameRepresentsPath ? GetPathFromSpecificName(controllerContext, name, cacheKey, ref searchedLocations) : GetPathFromGeneralName(controllerContext, viewLocations, name, controllerName, areaName, cacheKey, ref searchedLocations); } } private string GetPathFromGeneralName(ControllerContext controllerContext, List<ViewLocation> locations, string name, string controllerName, string areaName, string cacheKey, ref string[] searchedLocations) { string result = String.Empty; searchedLocations = new string[locations.Count]; for (int i = 0; i < locations.Count; i++) { ViewLocation location = locations[i]; string virtualPath = location.Format(name, controllerName, areaName); DisplayInfo virtualPathDisplayInfo = DisplayModeProvider.GetDisplayInfoForVirtualPath(virtualPath, controllerContext.HttpContext, path => FileExists(controllerContext, path), controllerContext.DisplayMode); if (virtualPathDisplayInfo != null) { string resolvedVirtualPath = virtualPathDisplayInfo.FilePath; searchedLocations = _emptyLocations; result = resolvedVirtualPath; ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, virtualPathDisplayInfo.DisplayMode.DisplayModeId), result); if (controllerContext.DisplayMode == null) { controllerContext.DisplayMode = virtualPathDisplayInfo.DisplayMode; } // Populate the cache with the existing paths returned by all display modes. // Since we currently don't keep track of cache misses, if we cache view.aspx on a request from a standard browser // we don't want a cache hit for view.aspx from a mobile browser so we populate the cache with view.Mobile.aspx. IEnumerable<IDisplayMode> allDisplayModes = DisplayModeProvider.Modes; foreach (IDisplayMode displayMode in allDisplayModes) { if (displayMode.DisplayModeId != virtualPathDisplayInfo.DisplayMode.DisplayModeId) { DisplayInfo displayInfoToCache = displayMode.GetDisplayInfo(controllerContext.HttpContext, virtualPath, virtualPathExists: path => FileExists(controllerContext, path)); if (displayInfoToCache != null && displayInfoToCache.FilePath != null) { ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, displayInfoToCache.DisplayMode.DisplayModeId), displayInfoToCache.FilePath); } } } break; } searchedLocations[i] = virtualPath; } return result; } private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref string[] searchedLocations) { string result = name; if (!(FilePathIsSupported(name) && FileExists(controllerContext, name))) { result = String.Empty; searchedLocations = new[] { name }; } ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, result); return result; } private bool FilePathIsSupported(string virtualPath) { if (FileExtensions == null) { // legacy behavior for custom ViewEngine that might not set the FileExtensions property return true; } else { // get rid of the '.' because the FileExtensions property expects extensions withouth a dot. string extension = GetExtensionThunk(virtualPath).TrimStart('.'); return FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); } } private static List<ViewLocation> GetViewLocations(string[] viewLocationFormats, string[] areaViewLocationFormats) { List<ViewLocation> allLocations = new List<ViewLocation>(); if (areaViewLocationFormats != null) { foreach (string areaViewLocationFormat in areaViewLocationFormats) { allLocations.Add(new AreaAwareViewLocation(areaViewLocationFormat)); } } if (viewLocationFormats != null) { foreach (string viewLocationFormat in viewLocationFormats) { allLocations.Add(new ViewLocation(viewLocationFormat)); } } return allLocations; } private static bool IsSpecificPath(string name) { char c = name[0]; return (c == '~' || c == '/'); } public virtual void ReleaseView(ControllerContext controllerContext, IView view) { IDisposable disposable = view as IDisposable; if (disposable != null) { disposable.Dispose(); } } private class AreaAwareViewLocation : ViewLocation { public AreaAwareViewLocation(string virtualPathFormatString) : base(virtualPathFormatString) { } public override string Format(string viewName, string controllerName, string areaName) { return String.Format(CultureInfo.InvariantCulture, _virtualPathFormatString, viewName, controllerName, areaName); } } private class ViewLocation { protected string _virtualPathFormatString; public ViewLocation(string virtualPathFormatString) { _virtualPathFormatString = virtualPathFormatString; } public virtual string Format(string viewName, string controllerName, string areaName) { return String.Format(CultureInfo.InvariantCulture, _virtualPathFormatString, viewName, controllerName); } } } VirtualPathProviderViewEngine
public abstract class BuildManagerViewEngine : VirtualPathProviderViewEngine { private IBuildManager _buildManager; private IViewPageActivator _viewPageActivator; private IResolver<IViewPageActivator> _activatorResolver; protected BuildManagerViewEngine() : this(null, null, null) { } protected BuildManagerViewEngine(IViewPageActivator viewPageActivator) : this(viewPageActivator, null, null) { } internal BuildManagerViewEngine(IViewPageActivator viewPageActivator, IResolver<IViewPageActivator> activatorResolver, IDependencyResolver dependencyResolver) { if (viewPageActivator != null) { _viewPageActivator = viewPageActivator; } else { _activatorResolver = activatorResolver ?? new SingleServiceResolver<IViewPageActivator>( () => null, new DefaultViewPageActivator(dependencyResolver), "BuildManagerViewEngine constructor"); } } internal IBuildManager BuildManager { get { if (_buildManager == null) { _buildManager = new BuildManagerWrapper(); } return _buildManager; } set { _buildManager = value; } } protected IViewPageActivator ViewPageActivator { get { if (_viewPageActivator != null) { return _viewPageActivator; } _viewPageActivator = _activatorResolver.Current; return _viewPageActivator; } } protected override bool FileExists(ControllerContext controllerContext, string virtualPath) { return BuildManager.FileExists(virtualPath); } internal class DefaultViewPageActivator : IViewPageActivator { private Func<IDependencyResolver> _resolverThunk; public DefaultViewPageActivator() : this(null) { } public DefaultViewPageActivator(IDependencyResolver resolver) { if (resolver == null) { _resolverThunk = () => DependencyResolver.Current; } else { _resolverThunk = () => resolver; } } public object Create(ControllerContext controllerContext, Type type) { return _resolverThunk().GetService(type) ?? Activator.CreateInstance(type); } } }
public class RazorViewEngine : BuildManagerViewEngine { internal static readonly string ViewStartFileName = "_ViewStart"; public RazorViewEngine() : this(null) { } public RazorViewEngine(IViewPageActivator viewPageActivator) : base(viewPageActivator) { AreaViewLocationFormats = new[] { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" }; AreaMasterLocationFormats = new[] { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" }; AreaPartialViewLocationFormats = new[] { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" }; ViewLocationFormats = new[] { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" }; MasterLocationFormats = new[] { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" }; PartialViewLocationFormats = new[] { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" }; FileExtensions = new[] { "cshtml", "vbhtml", }; } protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath) { return new RazorView(controllerContext, partialPath, layoutPath: null, runViewStartPages: false, viewStartFileExtensions: FileExtensions, viewPageActivator: ViewPageActivator) { DisplayModeProvider = DisplayModeProvider }; } 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; } }
上述代碼中,GetPath方法檢查指定路徑下的.cshtml文件是否存在;若是不存在,則將視圖引擎檢查過的全部路徑封裝到ViewEngineResult對象,並在以後經過異常返回;不然,會經過RazorViewEngine類的CreateView方法中建立了一個 RazorView 對象,即:視圖對象。注意:以後在步驟3中經過執行該對象的Render方法,來對【視圖頁】(.cshtml文件)的內容進行處理。
上述中經過var view = new RazorView(controllerContext, viewPath,layoutPath: masterPath, runViewStartPages: true, viewStartFileExtensions:FileExtensions, viewPageActivator: ViewPageActivator){DisplayModeProvider = DisplayModeProvider};來建立RazorView對象,5個參數和1個屬性分別表示:
接下來,着重看一下參數viewPageActivator的值和屬性DisplayModeProvider值的獲取方式:
一、ViewPageActivator是類BuildManagerViewEngine中的一個屬性,它在以後建立的實例的過程相似於ControllerBuilder建立DefaultControllerFactory。因爲在Controller激活中已經對SingleServiceResolver<TService>和DependencyResolver分析過,這裏就不在敖述,僅貼出源碼!
public abstract class BuildManagerViewEngine : VirtualPathProviderViewEngine { private IViewPageActivator _viewPageActivator; private IResolver<IViewPageActivator> _activatorResolver; protected BuildManagerViewEngine() : this(null, null, null) { } protected BuildManagerViewEngine(IViewPageActivator viewPageActivator) : this(viewPageActivator, null, null) { } internal BuildManagerViewEngine(IViewPageActivator viewPageActivator, IResolver<IViewPageActivator> activatorResolver, IDependencyResolver dependencyResolver) { if (viewPageActivator != null) { _viewPageActivator = viewPageActivator; } else { _activatorResolver = activatorResolver ?? new SingleServiceResolver<IViewPageActivator>( () => null, new DefaultViewPageActivator(dependencyResolver), "BuildManagerViewEngine constructor"); } } protected IViewPageActivator ViewPageActivator { get { if (_viewPageActivator != null) { return _viewPageActivator; } _viewPageActivator = _activatorResolver.Current; return _viewPageActivator; } } //內部類 internal class DefaultViewPageActivator : IViewPageActivator { private Func<IDependencyResolver> _resolverThunk; public DefaultViewPageActivator() : this(null) { } public DefaultViewPageActivator(IDependencyResolver resolver) { if (resolver == null) { _resolverThunk = () => DependencyResolver.Current; } else { _resolverThunk = () => resolver; } } public object Create(ControllerContext controllerContext, Type type) { return _resolverThunk().GetService(type) ?? Activator.CreateInstance(type); } } }
internal class SingleServiceResolver<TService> : IResolver<TService> where TService : class { private Lazy<TService> _currentValueFromResolver; private Func<TService> _currentValueThunk; private TService _defaultValue; private Func<IDependencyResolver> _resolverThunk; private string _callerMethodName; public SingleServiceResolver(Func<TService> currentValueThunk, TService defaultValue, string callerMethodName) { if (currentValueThunk == null) { throw new ArgumentNullException("currentValueThunk"); } if (defaultValue == null) { throw new ArgumentNullException("defaultValue"); } _resolverThunk = () => DependencyResolver.Current; _currentValueFromResolver = new Lazy<TService>(GetValueFromResolver); _currentValueThunk = currentValueThunk; _defaultValue = defaultValue; _callerMethodName = callerMethodName; } internal SingleServiceResolver(Func<TService> staticAccessor, TService defaultValue, IDependencyResolver resolver, string callerMethodName) : this(staticAccessor, defaultValue, callerMethodName) { if (resolver != null) { _resolverThunk = () => resolver; } } public TService Current { get { return _currentValueFromResolver.Value ?? _currentValueThunk() ?? _defaultValue; } } private TService GetValueFromResolver() { TService result = _resolverThunk().GetService<TService>(); if (result != null && _currentValueThunk() != null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, MvcResources.SingleServiceResolver_CannotRegisterTwoInstances, typeof(TService).Name.ToString(), _callerMethodName)); } return result; } }
public class DependencyResolver { private static DependencyResolver _instance = new DependencyResolver(); private IDependencyResolver _current; /// <summary> /// Cache should always be a new CacheDependencyResolver(_current). /// </summary> private CacheDependencyResolver _currentCache; public DependencyResolver() { InnerSetResolver(new DefaultDependencyResolver()); } public static IDependencyResolver Current { get { return _instance.InnerCurrent; } } internal static IDependencyResolver CurrentCache { get { return _instance.InnerCurrentCache; } } public IDependencyResolver InnerCurrent { get { return _current; } } /// <summary> /// Provides caching over results returned by Current. /// </summary> internal IDependencyResolver InnerCurrentCache { get { return _currentCache; } } public static void SetResolver(IDependencyResolver resolver) { _instance.InnerSetResolver(resolver); } public static void SetResolver(object commonServiceLocator) { _instance.InnerSetResolver(commonServiceLocator); } [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types.")] public static void SetResolver(Func<Type, object> getService, Func<Type, IEnumerable<object>> getServices) { _instance.InnerSetResolver(getService, getServices); } public void InnerSetResolver(IDependencyResolver resolver) { if (resolver == null) { throw new ArgumentNullException("resolver"); } _current = resolver; _currentCache = new CacheDependencyResolver(_current); } public void InnerSetResolver(object commonServiceLocator) { if (commonServiceLocator == null) { throw new ArgumentNullException("commonServiceLocator"); } Type locatorType = commonServiceLocator.GetType(); MethodInfo getInstance = locatorType.GetMethod("GetInstance", new[] { typeof(Type) }); MethodInfo getInstances = locatorType.GetMethod("GetAllInstances", new[] { typeof(Type) }); if (getInstance == null || getInstance.ReturnType != typeof(object) || getInstances == null || getInstances.ReturnType != typeof(IEnumerable<object>)) { throw new ArgumentException( String.Format( CultureInfo.CurrentCulture, MvcResources.DependencyResolver_DoesNotImplementICommonServiceLocator, locatorType.FullName), "commonServiceLocator"); } var getService = (Func<Type, object>)Delegate.CreateDelegate(typeof(Func<Type, object>), commonServiceLocator, getInstance); var getServices = (Func<Type, IEnumerable<object>>)Delegate.CreateDelegate(typeof(Func<Type, IEnumerable<object>>), commonServiceLocator, getInstances); InnerSetResolver(new DelegateBasedDependencyResolver(getService, getServices)); } [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types.")] public void InnerSetResolver(Func<Type, object> getService, Func<Type, IEnumerable<object>> getServices) { if (getService == null) { throw new ArgumentNullException("getService"); } if (getServices == null) { throw new ArgumentNullException("getServices"); } InnerSetResolver(new DelegateBasedDependencyResolver(getService, getServices)); } /// <summary> /// Wraps an IDependencyResolver and ensures single instance per-type. /// </summary> /// <remarks> /// Note it's possible for multiple threads to race and call the _resolver service multiple times. /// We'll pick one winner and ignore the others and still guarantee a unique instance. /// </remarks> private sealed class CacheDependencyResolver : IDependencyResolver { private readonly ConcurrentDictionary<Type, object> _cache = new ConcurrentDictionary<Type, object>(); private readonly ConcurrentDictionary<Type, IEnumerable<object>> _cacheMultiple = new ConcurrentDictionary<Type, IEnumerable<object>>(); private readonly IDependencyResolver _resolver; public CacheDependencyResolver(IDependencyResolver resolver) { _resolver = resolver; } public object GetService(Type serviceType) { return _cache.GetOrAdd(serviceType, _resolver.GetService); } public IEnumerable<object> GetServices(Type serviceType) { return _cacheMultiple.GetOrAdd(serviceType, _resolver.GetServices); } } private class DefaultDependencyResolver : IDependencyResolver { [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This method might throw exceptions whose type we cannot strongly link against; namely, ActivationException from common service locator")] public object GetService(Type serviceType) { // Since attempting to create an instance of an interface or an abstract type results in an exception, immediately return null // to improve performance and the debugging experience with first-chance exceptions enabled. if (serviceType.IsInterface || serviceType.IsAbstract) { return null; } try { return Activator.CreateInstance(serviceType); } catch { return null; } } public IEnumerable<object> GetServices(Type serviceType) { return Enumerable.Empty<object>(); } } private class DelegateBasedDependencyResolver : IDependencyResolver { private Func<Type, object> _getService; private Func<Type, IEnumerable<object>> _getServices; public DelegateBasedDependencyResolver(Func<Type, object> getService, Func<Type, IEnumerable<object>> getServices) { _getService = getService; _getServices = getServices; } [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This method might throw exceptions whose type we cannot strongly link against; namely, ActivationException from common service locator")] public object GetService(Type type) { try { return _getService.Invoke(type); } catch { return null; } } public IEnumerable<object> GetServices(Type type) { return _getServices(type); } } }
二、DisplayModeProvider是VirtualPathProviderViewEngine類的一個屬性。
public abstract class VirtualPathProviderViewEngine : IViewEngine { //省略其餘代碼 private DisplayModeProvider _displayModeProvider; protected internal DisplayModeProvider DisplayModeProvider { get { return _displayModeProvider ?? DisplayModeProvider.Instance; } set { _displayModeProvider = value; } } }
public sealed class DisplayModeProvider { public static readonly string MobileDisplayModeId = "Mobile"; public static readonly string DefaultDisplayModeId = string.Empty; private static readonly object _displayModeKey = new object(); private static readonly DisplayModeProvider _instance = new DisplayModeProvider(); private readonly List<IDisplayMode> _displayModes; public bool RequireConsistentDisplayMode { get; set; } public static DisplayModeProvider Instance { get { return DisplayModeProvider._instance; } } public IList<IDisplayMode> Modes { get { return this._displayModes; } } internal DisplayModeProvider() { List<IDisplayMode> list = new List<IDisplayMode>(); List<IDisplayMode> arg_37_0 = list; DefaultDisplayMode defaultDisplayMode = new DefaultDisplayMode(DisplayModeProvider.MobileDisplayModeId); defaultDisplayMode.ContextCondition = ((HttpContextBase context) => context.GetOverriddenBrowser().IsMobileDevice); arg_37_0.Add(defaultDisplayMode); list.Add(new DefaultDisplayMode()); this._displayModes = list; base..ctor(); } public IEnumerable<IDisplayMode> GetAvailableDisplayModesForContext(HttpContextBase httpContext, IDisplayMode currentDisplayMode) { return this.GetAvailableDisplayModesForContext(httpContext, currentDisplayMode, this.RequireConsistentDisplayMode).ToList<IDisplayMode>(); } internal IEnumerable<IDisplayMode> GetAvailableDisplayModesForContext(HttpContextBase httpContext, IDisplayMode currentDisplayMode, bool requireConsistentDisplayMode) { IEnumerable<IDisplayMode> source = (requireConsistentDisplayMode && currentDisplayMode != null) ? this.Modes.SkipWhile((IDisplayMode mode) => mode != currentDisplayMode) : this.Modes; return from mode in source where mode.CanHandleContext(httpContext) select mode; } public DisplayInfo GetDisplayInfoForVirtualPath(string virtualPath, HttpContextBase httpContext, Func<string, bool> virtualPathExists, IDisplayMode currentDisplayMode) { return this.GetDisplayInfoForVirtualPath(virtualPath, httpContext, virtualPathExists, currentDisplayMode, this.RequireConsistentDisplayMode); } internal DisplayInfo GetDisplayInfoForVirtualPath(string virtualPath, HttpContextBase httpContext, Func<string, bool> virtualPathExists, IDisplayMode currentDisplayMode, bool requireConsistentDisplayMode) { IEnumerable<IDisplayMode> availableDisplayModesForContext = this.GetAvailableDisplayModesForContext(httpContext, currentDisplayMode, requireConsistentDisplayMode); return ( from mode in availableDisplayModesForContext select mode.GetDisplayInfo(httpContext, virtualPath, virtualPathExists)).FirstOrDefault((DisplayInfo info) => info != null); } internal static IDisplayMode GetDisplayMode(HttpContextBase context) { if (context == null) { return null; } return context.Items[DisplayModeProvider._displayModeKey] as IDisplayMode; } internal static void SetDisplayMode(HttpContextBase context, IDisplayMode displayMode) { if (context != null) { context.Items[DisplayModeProvider._displayModeKey] = displayMode; } } }
③ 處理視圖頁中的內容,並將處理後的內容響應給客戶端。
public abstract class ViewResultBase : ActionResult { public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } //若是沒有指定視圖頁的名稱將當前Action做爲視圖頁的名稱 if (String.IsNullOrEmpty(ViewName)) { ViewName = context.RouteData.GetRequiredString("action"); } ViewEngineResult result = null; if (View == null) { //經過視圖引擎去建立視圖對象,並將視圖對象和該視圖相關的信息封裝在ViewEngineResult對象中。 result = FindView(context); View = result.View; } TextWriter writer = context.HttpContext.Response.Output; //將上下文、視圖對象、以前定義的ViewData、以前定義的TempData、writer封裝到一個ViewContext對象中。 //注意:ViewData對象中包含了咱們定義的ViewData鍵值對,還有咱們建立ActionResult對象時的參數值(也就是要在Razor中用的模型)。 ViewContext viewContext = new ViewContext(context, View, ViewData, TempData, writer); //使用指定的編寫器對象來呈現指定的視圖上下文,即:處理視圖頁中的內容,再經過writer將處理後的內容響應給客戶端。 View.Render(viewContext, writer); if (result != null) { //釋放視圖頁資源 result.ViewEngine.ReleaseView(context, View); } } }
上述代碼能夠看出,View.Render(viewContext,writer)方法是處理視圖頁面的入口,對於視圖頁(.cshtml文件),會將其編譯成繼承自WebViewPage<object>的類(如:~/Views/Foo/Action1.cshtml被編譯後的類名稱爲:_Page_Views_foo_Action1_cshtml),被編譯以後的文件以 .dll 的形式保存在臨時文件夾中。關於這個臨時文件夾,若是以Visual Studio Development Server爲宿主,則存放臨時文件夾目錄爲:「%WinDir%\Microsoft.NET\Framework\{VersionNo}\Temporary ASP.NET Files\root\目錄下;若是是以IIS爲宿主,則臨時文件存放在目錄「%WinDir%\Microsoft.NET\Framework\{VersionNo}\Temporary ASP.NET Files\Web應用名稱\xxx\xxx」下,例如:名稱爲MVCAppTest的Web應用,其臨時文件存放在路徑:C:\Windows\Microsoft.NET\Framework\{VersionNo}\Temporary ASP.NET Files\mvcapptest\c4ea0afa\a83bd407下的App_Web_xxxxx.dll,這個臨時目錄地址還能夠經過WebConfig文件來配置,以下:更多詳細介紹請猛擊這裏
<configuration> <system.web> <compilation tempDirectory="D;\Temporary\"></compilation> </system.web> </configuration>
除此以外,應該瞭解在【View呈現】的部分,對視圖頁的編譯順序爲:【_ViewStart.cshtml】-->【母板頁】-->【被請求的視圖頁】。若是是利用WebMatrix開發的話,則對視圖頁的編譯順序爲:【_PageStart.cshtml】-->【母板頁】-->【被請求的視圖頁】。
編譯視圖頁時,@{ ... }會被編譯成方法,@Html.xxx也被編譯成xxx方法。
瞭解上述內容以後,咱們再來看看在整個過程在源碼中的體現,如上代碼所知,View.Render(viewContext,writer)是處理視圖頁面的入口,此處的View就是在第二部分中咱們建立的RazorView對象,那麼接下來咱們就來看看它們的源碼,查看究竟是如何實現處理視圖頁面並返回給客戶端的!
主要執行代碼有:
public abstract class BuildManagerCompiledView : IView { public void Render(ViewContext viewContext, TextWriter writer) { if (viewContext == null) { throw new ArgumentNullException("viewContext"); } object instance = null; //獲取被編譯的視圖頁(cshtml文件)的類型 Type type = BuildManager.GetCompiledType(ViewPath); if (type != null) { //經過SingleServiceResolver利用反射和緩存的原理建立視圖頁的實例,過程相似SingleServiceResolver類對Controller的激活。 instance = ViewPageActivator.Create(_controllerContext, type); } if (instance == null) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentCulture, MvcResources.CshtmlView_ViewCouldNotBeCreated, ViewPath)); } //執行RenderView方法 RenderView(viewContext, writer, instance); } } public class RazorView : BuildManagerCompiledView { protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance) { if (writer == null) { throw new ArgumentNullException("writer"); } //將視圖頁實例轉換爲WebViewPage對象。 WebViewPage webViewPage = instance as WebViewPage; if (webViewPage == null) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentCulture, MvcResources.CshtmlView_WrongViewBase, ViewPath)); } webViewPage.OverridenLayoutPath = LayoutPath;//母板路徑(在第二步中,建立RazorView對象時,由構造函數導入) webViewPage.VirtualPath = ViewPath; //該視圖路徑 webViewPage.ViewContext = viewContext; //視圖上下文,其中有TempData、controllerContext、ViewData等 webViewPage.ViewData = viewContext.ViewData; //WebViewPage對象初始化,執行建立3個xxHelper對象AjaxHelper、HtmlHelper、UrlHelper。 webViewPage.InitHelpers(); if (VirtualPathFactory != null) { webViewPage.VirtualPathFactory = VirtualPathFactory; } if (DisplayModeProvider != null) { webViewPage.DisplayModeProvider = DisplayModeProvider; } WebPageRenderingBase startPage = null; //true,該值指示視圖啓動文件是否應在視圖以前執行(Razor視圖:true;WebForm視圖:false) if (RunViewStartPages) { //編譯_ViewStart.cshtml並經過反射建立一個StartPage類型實例,而後再將webViewPage賦值到其ChildPage屬性中,最後再轉爲WebPageRenderingBase類型。 //目的:將多個視圖對象按照層次封裝在一塊兒,以便以後進行內容的處理。 //StartPageLookup是一個委託,執行的StartPage類的靜態方法GetStartPage //參數:ViewStartFileName是RazorViewEngine類中定義的一個只讀字符串「_ViewStart」 //參數:ViewStartFileExtensions是個數組,包含「cshtml」和「vbhtml」 startPage = StartPageLookup(webViewPage, RazorViewEngine.ViewStartFileName, ViewStartFileExtensions); } //按層次結構處理視圖頁的內容。 //先處理_ViewStart.cshtml內容,而後將其中設置的PageData["key"]等值封裝到視圖上下文中,再去處理被請求頁的內容。 //參數:頁的上下文數據、要用於編寫執行 HTML 的編寫器、在頁層次結構中開始執行的頁 webViewPage.ExecutePageHierarchy(new WebPageContext(context: viewContext.HttpContext, page: null, model: null), writer, startPage); } }
上述代碼中,有兩處須要重點分析:
一、startPage=StartPageLookup(webViewPage,RazorViewEngine.ViewStartFileName,ViewStartFileExtensions)
StartPageLookup是一個委託,執行該委託就是調用StartPage類的靜態方法GetStartPage,該方法內編譯_ViewPage.cshtml並經過反射將其建立爲一個StartPage實例(繼承自WebPageRenderingBase),以後將以前建立的被請求的視圖頁對象賦值到StartPage實例的ChildPage屬性中,最後將StartPage實例轉換爲WebPageRenderingBase類型。
public abstract class StartPage : WebPageRenderingBase { //page:被請求的視圖頁對象;fileName:「_ViewStart」;supportedExtensions:數組「cshtml」和「vbhtml」 public static WebPageRenderingBase GetStartPage(WebPageRenderingBase page, string fileName, IEnumerable<string> supportedExtensions) { if (page == null) { throw new ArgumentNullException("page"); } if (string.IsNullOrEmpty(fileName)) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, CommonResources.Argument_Cannot_Be_Null_Or_Empty, new object[] { "fileName" }), "fileName"); } if (supportedExtensions == null) { throw new ArgumentNullException("supportedExtensions"); } return StartPage.GetStartPage(page, page.VirtualPathFactory ?? VirtualPathFactoryManager.Instance, HttpRuntime.AppDomainAppVirtualPath, fileName, supportedExtensions); } internal static WebPageRenderingBase GetStartPage(WebPageRenderingBase page, IVirtualPathFactory virtualPathFactory, string appDomainAppVirtualPath, string fileName, IEnumerable<string> supportedExtensions) { WebPageRenderingBase webPageRenderingBase = page; //獲得視圖路徑的目錄,page.VirtualPath是被請求的視圖頁的路徑。 string directory = VirtualPathUtility.GetDirectory(page.VirtualPath); while (!string.IsNullOrEmpty(directory) && directory != "/" && PathUtil.IsWithinAppRoot(appDomainAppVirtualPath, directory)) { //循環「cshtml」和「vbhtml」 foreach (string current in supportedExtensions) { string virtualPath = VirtualPathUtility.Combine(directory, fileName + "." + current); if (virtualPathFactory.Exists(virtualPath)) { //建立「_ViewStart.cshtml」的實例。 StartPage startPage = virtualPathFactory.CreateInstance(virtualPath); startPage.VirtualPath = virtualPath; //將被請求的視圖頁對象保存到StartPage對象的ChildPage屬性 startPage.ChildPage = webPageRenderingBase; startPage.VirtualPathFactory = virtualPathFactory; //將webPageRenderingBase的值設置爲當前StartPage對象。 webPageRenderingBase = startPage; break; } } directory = webPageRenderingBase.GetDirectory(directory); } return webPageRenderingBase; } }
注:以下圖所示,被請求的視圖頁對象繼承WebViewPage<TModel>,而「_ViewStart.cshtml」經編譯後建立的對象繼承自StartPage。
二、webViewPage.ExecutePageHierarchy(new WebPageContext(context: viewContext.HttpContext, page: null, model: null), writer, startPage)
此步驟的功能有:處理_StartView.cshtml的內容--->處理視圖頁的內容--->處理母板頁的內容。將前兩個內容處理完成以後,若是存在母板頁,則處理母板頁,以後再將前兩個內容添加到母板頁內容中,最後執行輸出。
此句代碼執行視圖頁對象的ExecutePageHierarchy方法,由上圖中的關係所示,此方法被定義在基類WebPageBase中。(參數startPage是「_ViewStart.cshtml」通過編號後建立的對象,且又將視圖頁對象webViewPage賦值給其ChildPage屬性)。
public abstract class WebPageBase : WebPageRenderingBase { public void ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer) { WebPageRenderingBase startPage = null; this.ExecutePageHierarchy(pageContext, writer, startPage); } public void ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage) { this.PushContext(pageContext, writer); //若是Web應用中包含_ViewStartPage.cshtml if (startPage != null) { //對於Razor,此處的this指的是 被請求的視圖頁對象,而startPage則是_ViewStart.cshtml被編譯並反射獲得的對象 //顯然此處永遠是不相等的。 if (startPage != this) { IDictionary<object, object> pageData = null; object model = null; bool isLayoutPage = false; WebPageContext webPageContext = WebPageContext.CreateNestedPageContext<object>(pageContext, pageData, model, isLayoutPage); webPageContext.Page = startPage; startPage.PageContext = webPageContext; } //執行「_ViewStart.cshtml」經編譯後的建立的對象的ExecutePageHierarchy方法。即:執行StartPage類中定義的ExecutePageHierarchy方法 //startPage類型繼承自StartPage,而不是繼承自WebViewPage<TModel> startPage.ExecutePageHierarchy(); } //Web應用中不包含_ViewStart.cshtml else { //執行WebViewPage類中定義的ExecutePageHierarchy()方法 this.ExecutePageHierarchy(); } //處理母板頁(佈局頁) this.PopContext(); } }
處理「_ViewStart.cshtml」和被請求視圖頁的內容:
如上代碼所示,當Web應用中使用了"_ViewStart.cshtml"時,下一步就是執行StartPage類中定義的ExecutePageHierarchy(),不然,就要執行WebViewPage類中定義的ExecutePageHiererchy()方法。下面就來分析下Web應用中使用了"_ViewStart.cshtml"狀況下,代碼的執行過程。(因爲其中包含了對 被請求視圖頁對象的處理,即:會執行WebViewPage類的ExecutePageHiererchy方法,因此對於Web應用中不包含「_ViewStart.cshtml」狀況再也不單獨介紹!)
public abstract class StartPage : WebPageRenderingBase { public WebPageRenderingBase ChildPage { get; set; } public override HttpContextBase Context { get { return this.ChildPage.Context; } set { this.ChildPage.Context = value; } } internal bool RunPageCalled { get; set; } public override void ExecutePageHierarchy() { //在指定的 HTTP 上下文中的堆棧頂部插入模板文件 TemplateStack.Push(this.Context, this); try { //執行「_ViewStart.cshtml」通過編譯後類的Execute()方法,來執行網頁上定義的方法。 this.Execute(); //this.RunPageCalled默認爲false if (!this.RunPageCalled) { //執行ChildPage頁的ExecutePageHierarchy方法,也就是去執行被請求的視圖頁對象的Execute()方法. this.RunPage(); } } finally { //刪除並返回位於指定的 HTTP 上下文中的堆棧頂部的模板文件 TemplateStack.Pop(this.Context); } } public void RunPage() { this.RunPageCalled = true; //這個ChildPage就是被請求的視圖頁對象,因此就是去執行其父類WebViewPage中的ExecutePageHierarchy()方法 this.ChildPage.ExecutePageHierarchy(); } public override void Write(HelperResult result) { this.ChildPage.Write(result); } public override void WriteLiteral(object value) { this.ChildPage.WriteLiteral(value); } public override void Write(object value) { this.ChildPage.Write(value); } }
上面代碼StartPage類的ExecutePageHierarchy方法中調用了【this.Execute()】,該方法是「_ViewStart.cshtml」文件經編譯後生成的!該方法的內部會執行StartPage類的Write和WriteLiteral方法。而有上面的StartPage類的源碼可知,這些方法又會調用This.ChildPage.Write和This.ChildPage.WriteLiteral,即:被請求的視圖頁對象對應的方法,以實現將「_ViewStart.cshtml」中的內容輸出到 被請求的視圖頁 中。
以下圖所示的「_ViewStart.cshtml」經編譯後的結果爲:
上面代碼StartPage類的ExecutePageHierarchy方法中接着又要調用【this.RunPage()】方法,該方法內部又調用【this.ChildPage.ExecutePageHierarchy()】,也就是被請求視圖頁對象的ExecutePageHierarchy()方法,實如今WebViewPage類中。
public abstract class WebViewPage : WebPageBase, IViewDataContainer, IViewStartPageChild { public override void ExecutePageHierarchy() { TextWriter writer = this.ViewContext.Writer; this.ViewContext.Writer = base.Output; //執行基類WebPageBase中的ExecutePageHierarchy() base.ExecutePageHierarchy(); if (!string.IsNullOrEmpty(this.OverridenLayoutPath)) { this.Layout = this.OverridenLayoutPath; } this.ViewContext.Writer = writer; } }
public abstract class WebPageBase : WebPageRenderingBase { public override void ExecutePageHierarchy() { if (WebPageHttpHandler.ShouldGenerateSourceHeader(this.Context)) { try { string virtualPath = this.VirtualPath; if (virtualPath != null) { string text = this.Context.Request.MapPath(virtualPath); if (!text.IsEmpty()) { base.PageContext.SourceFiles.Add(text); } } } catch { } } TemplateStack.Push(this.Context, this); try { this.Execute(); } finally { TemplateStack.Pop(this.Context); } } }
在WebPageBase的ExecutePageHierarchy()方法中又調用了【this.Execute()】,也就是執行 被請求視圖頁對象的Execute()方法!而後在Execute方法中也會調用This.Write或This.WriteLiteral方法將視圖頁中的內容輸出。
以下圖所示被請求的視圖頁或母板頁經編譯後的結果爲:
以上的這些WriteLiteral方法或Write方法,都是使用HttpWriter對象的Write方法將內容寫入到Http輸出流中。(以後再經過IIS將響應流中的數據返回給客戶端)
處理母板頁內容:
也就是執行this.PopContext();
public abstract class WebPageBase : WebPageRenderingBase { public void PopContext() { //獲取"_ViewStart.cshtml"和視圖頁的內容 string renderedContent = this._tempWriter.ToString(); this.OutputStack.Pop(); //若是有母板頁 if (!string.IsNullOrEmpty(this.Layout)) { //母板頁名稱 string partialViewName = base.NormalizeLayoutPagePath(this.Layout); this.OutputStack.Push(this._currentWriter); //處理母板頁 this.RenderSurrounding(partialViewName, delegate(TextWriter w) { w.Write(renderedContent); }); this.OutputStack.Pop(); } //沒有母板頁 else { this._currentWriter.Write(renderedContent); } this.VerifyRenderedBodyOrSections(); this.SectionWritersStack.Pop(); } private void RenderSurrounding(string partialViewName, Action<TextWriter> body) { Action<TextWriter> bodyAction = base.PageContext.BodyAction; base.PageContext.BodyAction = body; bool isLayoutPage = true; object[] data = new object[0]; this.Write(this.RenderPageCore(partialViewName, isLayoutPage, data)); base.PageContext.BodyAction = bodyAction; } private HelperResult RenderPageCore(string path, bool isLayoutPage, object[] data) { if (string.IsNullOrEmpty(path)) { throw ExceptionHelper.CreateArgumentNullOrEmptyException("path"); } return new HelperResult(delegate(TextWriter writer) { path = this.NormalizePath(path); WebPageBase webPageBase = this.CreatePageFromVirtualPath(path, this.Context, new Func<string, bool>(this.VirtualPathFactory.Exists), this.DisplayModeProvider, this.DisplayMode); WebPageContext pageContext = this.CreatePageContextFromParameters(isLayoutPage, data); webPageBase.ConfigurePage(this); //熟悉吧,這個就是去處理母板頁的入口!(和視圖頁的處理相同) webPageBase.ExecutePageHierarchy(pageContext, writer); }); } }
母板頁和視圖頁相同,被編譯後都是繼承自 WebViewPage<object>的類,因此其具體的處理過程和視圖頁是相同的!
下圖是母板頁的編譯結果:
以上就是ViewResult進行View呈現時的所有內容,若是感興趣的話, 本身能夠分析下PartialView的執行過程,對於上述不適之處,請指正!!!