这里特别感谢 swagon 提到了Displaymodeprovider,所以才有了本篇博客,也使我对【View的呈现】中寻找视图页的过程有了清晰的认识!
前戏
在MVC中,执行完Action之后,会返回一个ActionResult对象,之后再执行该对象的ExecuteResult方法,这也就是【View的呈现】的入口!
【View的呈现】包括了:根据模版去寻找请求的视图页、编译视图页、再执行视图页的内容。本篇就来介绍寻找视图页的详细过程,其中涉及到了MVC 4的一个新特性--“手机视图页”
public abstract class ViewResultBase : ActionResult { public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } if (String.IsNullOrEmpty(ViewName)) { ViewName = context.RouteData.GetRequiredString("action"); } ViewEngineResult result = null; if (View == null) { //通过视图引擎去创建视图对象,并将视图对象和该视图相关的信息封装在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 { protected override ViewEngineResult FindView(ControllerContext context) { //寻找当前请求的视图页,如果能找到则创建视图对象。 //遍历每个视图引擎(默认有两个WebFormEngine、RazorViewEngine),并执行每一个视图引擎的FindView方法。 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)); } }
ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName);则是遍历每个视图引擎(默认有两个WebFormEngine、RazorViewEngine),并执行每一个视图引擎的FindView方法。
注:在执行视图引擎的FindView方法时,先按照从缓存表中找是否存在请求的视图页,如果没有的话,再进行一次寻找!
下面以RazorViewEngine为例:
public abstract class VirtualPathProviderViewEngine : IViewEngine { //useCache先是true,该方法返回的是null。则让useCache=false,再执行一遍。即:先通过缓存去找,如果没有找到的话,就正经的去找。 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"); //获取视图的路径,这里就是咱们本篇博文的内容的入口点!!!!!!!!!!!!!!!!!!! //ViewLocationFormats、AreaViewLocationFormats定义在派生类RazorViewEngine类中。 //ViewLocationFormats:"~/Views/{1}/{0}.cshtml","~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml","~/Views/Shared/{0}.vbhtml" //AreaViewLocationFormats:"~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml","~/Areas/{2}/Views/Shared/{0}.vbhtml" string viewPath = GetPath(controllerContext, ViewLocationFormats, AreaViewLocationFormats, "ViewLocationFormats", viewName, controllerName, CacheKeyPrefixView, useCache, out viewLocationsSearched); string masterPath = GetPath(controllerContext, MasterLocationFormats, AreaMasterLocationFormats, "MasterLocationFormats", masterName, controllerName, CacheKeyPrefixMaster, useCache, out masterLocationsSearched); if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName))) { return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched)); } return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this); } }
啪啪啪
GetPath方法在寻找【视图页】时,首先将当前请求的Controller和Action的名称添加到地址格式化器中,这样就有了要寻找的地址(们),之后就来检查格式化后的地址是否真的存在指定的【视图页】。如果是通过手机端来请求,则会对格式化之后的地址进行再进行处理(如:Index.cshtml修改为Index.Mobile.cshtml),之后再检查新地址下是否存在【视图页】。
注:默认情况下会先检查是否为手机端访问!
口说无凭,上源码吧:
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Web.Hosting; using System.Web.Mvc.Properties; using System.Web.WebPages; namespace System.Web.Mvc { 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 Func<VirtualPathProvider> _vppFunc = () => HostingEnvironment.VirtualPathProvider; internal Func<string, string> GetExtensionThunk = VirtualPathUtility.GetExtension; private IViewLocationCache _viewLocationCache; [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; } // Neither DefaultViewLocationCache.Null nor a DefaultViewLocationCache instance maintain internal state. Fine // if multiple threads race to initialize _viewLocationCache. public IViewLocationCache ViewLocationCache { get { if (_viewLocationCache == null) { if (HttpContext.Current == null || HttpContext.Current.IsDebuggingEnabled) { _viewLocationCache = DefaultViewLocationCache.Null; } else { _viewLocationCache = new DefaultViewLocationCache(); } } return _viewLocationCache; } set { if (value == null) { throw Error.ArgumentNull("value"); } _viewLocationCache = value; } } [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")] public string[] ViewLocationFormats { get; set; } // Likely exists for testing only protected VirtualPathProvider VirtualPathProvider { get { return _vppFunc(); } set { if (value == null) { throw Error.ArgumentNull("value"); } _vppFunc = () => value; } } // Provided for testing only; setter used in BuildManagerViewEngine but only for test scenarios internal Func<VirtualPathProvider> VirtualPathProviderFunc { get { return _vppFunc; } set { if (value == null) { throw Error.ArgumentNull("value"); } _vppFunc = value; } } protected internal DisplayModeProvider DisplayModeProvider { get { return _displayModeProvider ?? DisplayModeProvider.Instance; } set { _displayModeProvider = value; } } internal virtual 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); } //开始开始开始开始开始开始开始开始开始开始开始开始开始开始开始开始开始开始开始开始 //useCache先是true,该方法返回的是null。则让useCache=false,再执行一遍。即:先通过缓存去找,如果没有找到的话,就正经的去找。 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"); //获取视图的路径 //ViewLocationFormats、AreaViewLocationFormats定义在派生类RazorViewEngine类中。 //ViewLocationFormats:"~/Views/{1}/{0}.cshtml","~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml","~/Views/Shared/{0}.vbhtml" //AreaViewLocationFormats:"~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml","~/Areas/{2}/Views/Shared/{0}.vbhtml" string viewPath = GetPath(controllerContext, ViewLocationFormats, AreaViewLocationFormats, "ViewLocationFormats", viewName, controllerName, CacheKeyPrefixView, useCache, out viewLocationsSearched); string masterPath = GetPath(controllerContext, MasterLocationFormats, AreaMasterLocationFormats, "MasterLocationFormats", masterName, controllerName, CacheKeyPrefixMaster, useCache, out masterLocationsSearched); if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName))) { return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched)); } return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this); } //获取视图路径方法 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; } //从RouteData的DataTokens中获取key为‘area’的值 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) { //根据请求上下文获取可用的DisplayModelProvider IEnumerable<IDisplayMode> possibleDisplayModes = DisplayModeProvider.GetAvailableDisplayModesForContext(controllerContext.HttpContext, controllerContext.DisplayMode); foreach (IDisplayMode displayMode in possibleDisplayModes) { //displayMode.DisplayModeId就是“Mobile”,即:执行DefaultDisplayMode有参数的构造函数 string cachedLocation = ViewLocationCache.GetViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, displayMode.DisplayModeId)); if (cachedLocation == null) { // If any matching display mode location is not in the cache, fall back to the uncached behavior, which will repopulate all of our caches. return null; } // A non-empty cachedLocation indicates that we have a matching file on disk. Return that result. if (cachedLocation.Length > 0) { if (controllerContext.DisplayMode == null) { controllerContext.DisplayMode = displayMode; } return cachedLocation; } // An empty cachedLocation value indicates that we don't have a matching file on disk. Keep going down the list of possible display modes. } // GetPath is called again without using the cache. return null; } else { //如果ViewResult的参数的第一个字符是:~ 或 /,则执行第一个方法,否则执行第二个方法! //第一个方法:直接指定了路径去寻找 //第二个方法:只通过试图名称再拼接路径去寻找(用到了DisplayModeProvider) //第二个方法的viewLocations参数是含有8条数据的集合。 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]; //根据8种格式器创建路径 string virtualPath = location.Format(name, controllerName, areaName); //这里的controllerContext.DisplayMode属性执行DisplayModeProvider的GetDisplayMode、SetDisplayMode方法! 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 for all other display modes. We want to cache both file system hits and misses so that we can distinguish // in future requests whether a file's status was evicted from the cache (null value) or if the file doesn't exist (empty string). //再执行循环执行除了适合的那个DefaultDisplayMode之外的所有DefaultDisplayMode对象,将所有存在的路径封装到DisplayInfo对象中,并添加到缓存表中,以便之后请求时直接去缓存中获取。 //例如:当前是手机的请求,则会通过执行DisplayModeId=‘Mobile’的那个DefaultDisplayMode来获取【视图页】的路径(Index.Mobile.cshtml), // 但是在执行完成之后,还会执行DisplayModeId!=‘Mobile’所有其他的DefaultDisplayMode对象,也就是pc端请求时的路径(Index.cshtml),将其加入缓存表中,以便下次请求时直接去缓存表中获取。 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)); string cacheValue = String.Empty; if (displayInfoToCache != null && displayInfoToCache.FilePath != null) { cacheValue = displayInfoToCache.FilePath; } ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, displayMode.DisplayModeId), cacheValue); } } 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) { //将四个AreaAwareViewLocation和四个ViewLocation添加到 allLocations集合中!并返回 //AreaAwareViewLocation继承自ViewLocation 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); } } } }
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System.Collections.Generic; using System.Linq; namespace System.Web.WebPages { 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(); //默认情况下只有两个DefaultDisplayMode,可以自定义并添加到该集合中! private readonly List<IDisplayMode> _displayModes = new List<IDisplayMode> { new DefaultDisplayMode(MobileDisplayModeId) { ContextCondition = context => context.GetOverriddenBrowser().IsMobileDevice }, new DefaultDisplayMode() }; internal DisplayModeProvider() { // The type is a psuedo-singleton. A user would gain nothing from constructing it since we won't use anything but DisplayModeProvider.Instance internally. } /// <summary> /// Restricts the search for Display Info to Display Modes either equal to or following the current /// Display Mode in Modes. For example, a page being rendered in the Default Display Mode will not /// display Mobile partial views in order to achieve a consistent look and feel. /// </summary> public bool RequireConsistentDisplayMode { get; set; } public static DisplayModeProvider Instance { get { return _instance; } } /// <summary> /// All Display Modes that are available to handle a request. /// </summary> public IList<IDisplayMode> Modes { get { return _displayModes; } } //这个方法的功能:如果指定集合中的某个DefaultDisplayMode来处理请求的话,则直接从它开始。可以在HomeController中通过this.ControllerContext.DisplayMode来设置。 private int FindFirstAvailableDisplayMode(IDisplayMode currentDisplayMode, bool requireConsistentDisplayMode) { if (requireConsistentDisplayMode && currentDisplayMode != null) { //如果集合中么有和当前currentDisplayMode匹配的话,first值为 -1 int first = _displayModes.IndexOf(currentDisplayMode); return (first >= 0) ? first : _displayModes.Count; } return 0; } /// <summary> /// Returns any IDisplayMode that can handle the given request. /// </summary> public IEnumerable<IDisplayMode> GetAvailableDisplayModesForContext(HttpContextBase httpContext, IDisplayMode currentDisplayMode) { return GetAvailableDisplayModesForContext(httpContext, currentDisplayMode, RequireConsistentDisplayMode); } internal IEnumerable<IDisplayMode> GetAvailableDisplayModesForContext(HttpContextBase httpContext, IDisplayMode currentDisplayMode, bool requireConsistentDisplayMode) { int first = FindFirstAvailableDisplayMode(currentDisplayMode, requireConsistentDisplayMode); for (int i = first; i < _displayModes.Count; i++) { IDisplayMode mode = _displayModes[i]; if (mode.CanHandleContext(httpContext)) { yield return mode; } } } /// <summary> /// Returns DisplayInfo from the first IDisplayMode in Modes that can handle the given request and locate the virtual path. /// If currentDisplayMode is not null and RequireConsistentDisplayMode is set to true the search for DisplayInfo will only /// start with the currentDisplayMode. /// </summary> public DisplayInfo GetDisplayInfoForVirtualPath(string virtualPath, HttpContextBase httpContext, Func<string, bool> virtualPathExists, IDisplayMode currentDisplayMode) { //默认RequireConsistentDisplayMode为false return GetDisplayInfoForVirtualPath(virtualPath, httpContext, virtualPathExists, currentDisplayMode, RequireConsistentDisplayMode); } internal DisplayInfo GetDisplayInfoForVirtualPath(string virtualPath, HttpContextBase httpContext, Func<string, bool> virtualPathExists, IDisplayMode currentDisplayMode, bool requireConsistentDisplayMode) { // Performance sensitive int first = FindFirstAvailableDisplayMode(currentDisplayMode, requireConsistentDisplayMode); for (int i = first; i < _displayModes.Count; i++) { IDisplayMode mode = _displayModes[i]; if (mode.CanHandleContext(httpContext)) { DisplayInfo info = mode.GetDisplayInfo(httpContext, virtualPath, virtualPathExists); if (info != null) { return info; } } } return null; } internal static IDisplayMode GetDisplayMode(HttpContextBase context) { return context != null ? context.Items[_displayModeKey] as IDisplayMode : null; } internal static void SetDisplayMode(HttpContextBase context, IDisplayMode displayMode) { if (context != null) { context.Items[_displayModeKey] = displayMode; } } } }
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System.IO; namespace System.Web.WebPages { public class DefaultDisplayMode : IDisplayMode { //通过构造函数赋值,MVC会创建两个DefaultDisplayMode对象,将其中一个对象的该字段值设置为“Mobile” private readonly string _suffix; public DefaultDisplayMode() : this(DisplayModeProvider.DefaultDisplayModeId) { } public DefaultDisplayMode(string suffix) { _suffix = suffix ?? String.Empty; } public Func<HttpContextBase, bool> ContextCondition { get; set; } public virtual string DisplayModeId { get { return _suffix; } } public bool CanHandleContext(HttpContextBase httpContext) { return ContextCondition == null || ContextCondition(httpContext); } public virtual DisplayInfo GetDisplayInfo(HttpContextBase httpContext, string virtualPath, Func<string, bool> virtualPathExists) { //调用TransformPath方法,将寻找【View视图页】路径设置为 xxx.Mobile.cshtml string transformedFilename = TransformPath(virtualPath, _suffix); if (transformedFilename != null && virtualPathExists(transformedFilename)) { return new DisplayInfo(transformedFilename, this); } return null; } protected virtual string TransformPath(string virtualPath, string suffix) { if (String.IsNullOrEmpty(suffix)) { return virtualPath; } string extension = Path.GetExtension(virtualPath); return Path.ChangeExtension(virtualPath, suffix + extension); } } }
由以上源码可知,默认情况下,ASP.NET MVC4在DisplayModeProvider中定义了一个含有两个DefaultDisplayMode对象(用于对地址再处理)的集合:
public static readonly string MobileDisplayModeId = "Mobile"; private readonly List<IDisplayMode> _displayModes = new List<IDisplayMode> { new DefaultDisplayMode(MobileDisplayModeId) { ContextCondition = context => context.GetOverriddenBrowser().IsMobileDevice }, new DefaultDisplayMode() };
由于处理时,是按照遍历执行_displayModes集合中DefaultDisplayMode对象的GetDisplayInfo方法(索引值从0开始),所以无论是 PC 还是 Phone发送的请求,都会先执集合中的第一个DefaultDisplayMode对象(判断是否为手机的请求)。如果Phone端发送请求,会去寻找xxx.Mobile.cshtml,如果没有的话,就继续执行第二个DefaultDisplayMode对象,去寻找xxx.cshtml。如果是PC端发送请求,也是首先执行第一个DefaultDisplayMode对象,但是由于不满足 context => context.GetOverriddenBrowser().IsMobileDevice 条件,所以还是需要去执行第二个DefaultDisplayMode对象,去寻找xxx.cshtml。
扩展:
1、指定DisplayMode
模拟需求:对Phone端用户的某个Action请求,返回电脑版网页。
public ActionResult Index() { //一些判断条件 this.ControllerContext.DisplayMode = DisplayModeProvider.Instance.Modes[1]; DisplayModeProvider.Instance.RequireConsistentDisplayMode = true; return View(); }
根据上述设置,即使是Phone端的请求并且还存在Index.Mobile.cshtml文件,也会去执行Index.cshtml,即:实现Phone用户访问电脑版网页。
2、自定义DisplayMode
模拟需求:为Android 2.3用户设置特定的页面
先创建一个类似于Index.Android23.cshtml 的页面,然后在Global.asax中做如下设置即可:
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); AuthConfig.RegisterAuth(); DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("Android23") { ContextCondition = (context => context.GetOverriddenUserAgent().IndexOf ("Android 2.3", StringComparison.OrdinalIgnoreCase) >= 0) }); } }
以上就是所有内容,如有不适之处,请指正!!!