ASP.NET MVC 4 视图页去哪里儿
这里特别感谢 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[];
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 == )
{
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 > )
{
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 = ; 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[];
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
// 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 >= ) ? first : _displayModes.Count;
}
return ;
} /// <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;
}
}
}
}
DisplayModeProvider
// 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);
}
}
}
DefaultDisplayMode
由以上源码可知,默认情况下,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)
});
}
}
以上就是所有内容,如有不适之处,请指正!!!
ASP.NET MVC 4 视图页去哪里儿的更多相关文章
- ASP.NET MVC 5 - 视图
在本节中,你要去修改HelloWorldController类,使用视图模板文件,在干净利索地封装的过程中:客户端浏览器生成HTML. 您将创建一个视图模板文件,其中使用了ASP.NET MVC 3所 ...
- [转]ASP.NET MVC 5 - 视图
在本节中,你要去修改HelloWorldController类,使用视图模板文件,在干净利索地封装的过程中:客户端浏览器生成HTML. 您将创建一个视图模板文件,其中使用了ASP.NET MVC 3所 ...
- asp.net mvc 部分视图加载区别
ASP.NET MVC 部分视图 ASP.NET(11) 版权声明:本文为博主原创文章,未经博主允许不得转载. [部分视图] ASP.NET MVC 里的部分视图,相当于 Web Form 里的 ...
- Asp.net MVC Razor视图模版动态渲染PDF,Razor模版生成静态Html
Asp.net MVC Razor视图模版动态渲染PDF,Razor模版生成静态Html 1.前言 上一篇文章我开源了轮子,Asp.net Core 3.1 Razor视图模版动态渲染PDF,然后,很 ...
- ASP.NET MVC Razor视图引擎攻略
--引子 看下面一段MVC 2.0的代码. <%if (Model != null){%> <p><%=Model%></p><%}%>&l ...
- ASP.NET Mvc Razor视图语法
在ASP.NET MVC中有两套模版引擎,一套是ASPX,一套是Razor,从事过WebForms开发的朋友们,对于ASPX模版已经很熟悉了,下面我说一下我所熟悉的Razor模版引擎的一些语法,供大家 ...
- ASP.NET MVC 部分视图(转)
[部分视图] ASP.NET MVC 里的部分视图,相当于 Web Form 里的 User Control.我们的页面往往会有许多重用的地方,可以进行封装重用.使用 部分视图 : 1. 可以简写代 ...
- MvcMailer通过ASP.NET MVC Razor视图和基架发送邮件
MvcMailer是一个有趣的组件,您可以使用ASP.NET MVC框架在发送邮件.很重要的是,它使用Razor视图引擎的观点作为电子邮件模板和很容易安装和使用.在本文中你将看到如何安装,设置邮件模板 ...
- ASP.NET MVC编程——视图
1Razon语法 使用@符号后接C#或VB.NET语句的方式. 基本规则 1)变量 @后直接变量即可 2)代码块 为使用表达式或多行代码,@后跟大括号将多行代码包括在大括号中 3)"+&qu ...
随机推荐
- 图像分割算法-GraphSeg算法
图像分割是图像处理中的一个基础课题范围,本文简要介绍一种比较好的图像分割算法: Efficient Graph-Based Image Segmentation 论文出处:http://cs.brow ...
- 300ms延时
具体参考:http://www.jianshu.com/p/6e2b68a93c88 一,简单粗暴型:禁用缩放 <meta name="viewport" content=& ...
- 一些SQL
复制当前所有,ID自动增长. insert into test(name,pass) select name,pass from test 删除当前重复 delete A fro ...
- HBase change split policy on an existing table
hbase(main)::> create 'test_table_region', 'username' row(s) in 1.2150 seconds hbase(main)::> ...
- 2016 Multi-University Training Contest 1 C.Game
Game Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Submis ...
- [小工具]EquationCalcular
名称:EquationCalcular 版本:V1.0.0 更新日期:2015/9/27 简要介绍:本工具用于计算范围比较有限的方程及方程组,仅仅局限于n元一次方程组,欢迎需要的小学生和初中生来玩 ...
- 【BZOJ】3993: [SDOI2015]星际战争
题意 \(m\)个人\(n\)个物品,第\(i\)个物品生命值为\(A_i\),第\(i\)个人每秒可以减少一个物品\(B_i\)的生命值,给出一个\(m \times n\)的矩阵,如果\(i\)行 ...
- css样式
这是CSS的定义经常用到的样式: body{ width:100%; background:#000; text-align:center;font:12px "宋体", Aria ...
- MySQL函数操作数据库
1.select语句查询信息(实现模糊查询) <form name="form1" method="post" action=""&g ...
- Python2 下 Unicode 的一个小bug
关于Python的编码问题已经是老生常谈了,此处主要是介绍一个罕见的问题,也算是Python2的一个bug了(Python3不会有此问题). 在有时候我们去爬取网页或者调用一些第三方库获取文本的时候, ...