白话学习MVC(十)View的呈现二
本节将接着《白话学习MVC(九)View的呈现一》来继续对ViewResult的详细执行过程进行分析!
9、ViewResult
ViewResult将视图页的内容响应给客户端!
由于ViewResult的View呈现过程比较复杂,在此先大致描述一下整个流程:
- 获取视图引擎,默认有两个:ASPX引擎、Razor引擎。
- 根据视图页名称,通过视图引擎去检查是否存在对应的视图页,如果存在,则创建视图对象。如果不存在,则将所有视图引擎寻找过的路径作为异常返回。
- 创建视图对象之后,处理视图页中的内容(先处理_ViewStart.cshtml,之后再处理相应的试图页)。例如:TempData、Html.XXX等。
- 视图页内容处理完毕之后,就将视图内容作为响应返回给客户端。
注:由于对于ASP.NET MVC来说,xxx.cshtml文件称为视图,而对于视图引擎来说,实现IView接口的类称为视图,为了避免混淆,有如下定义:
【视图引擎】泛指那些实现了IViewEngine接口的类,如:WebFormViewEngine、RazorViewEngine。
【视图】泛指那些实现了IView接口的类,如:WebViewPage、RazorView。
【视图页】例如:xxx.cshtml文件或xxx.aspx文件。
【视图页的名称】指视图页的文件名。
对于ViewResult,它有一个父类ViewResultBase,其中定义着一些必要的属性和ExecuteResult方法!
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及继承的成员来逐步分析下执行流程:
① 当创建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个属性分别表示:
- controllerContext :控制器上下文
- viewPath :视图页的路径
- runViewStartPages :true,该值指示视图启动文件是否应在视图之前执行。
- viewStartFileExtensions :视图启动文件时将使用的扩展名集(.cshtml和.vbhtml)
- viewPageActivator :用来创建视图页对象实例(反射、缓存)
- DisplayModeProvider :默认情况值为一个DisplayModeProvider对象,通过DisplayModeProvider.Instance获取的。
接下来,着重看一下参数viewPageActivator的值和属性DisplayModeProvider值的获取方式:
1、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); } } }
2、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); } }
上述代码中,有两处需要重点分析:
1、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。
2、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的执行过程,对于上述不适之处,请指正!!!