在MVC 4 源代码中挖掘了一下之后,我找到了答案。
RazorViewEngine
派生自,而这个BuildManagerViewEngine
又派生自VirtualPathProviderViewEngine
。它是VirtualPathProviderViewEngine
实现该方法的方法FindView
:
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");
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
方法会做这样的事情:
return nameRepresentsPath
? GetPathFromSpecificName(controllerContext, name, cacheKey, ref searchedLocations)
: GetPathFromGeneralName(controllerContext, viewLocations, name, controllerName, areaName, cacheKey, ref searchedLocations);
到达那里!有趣的方法是GetPathFromGeneralName
,它试图为视图构建整个路径并检查该路径是否存在。该方法循环遍历在视图引擎中注册的每个视图位置,使用对当前 HttpContext 有效的显示模式更新视图路径,然后检查解析的路径是否存在。如果是这样,则已找到视图,将其分配给结果,缓存并返回结果路径。
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 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).
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;
}
您可能已经注意到,我还没有讨论带有以下注释的一段代码(为了清晰起见重新格式化):
// 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).
那(以及注释下方的代码:))意味着一旦 MVC 4 从视图引擎中注册的视图位置中找到了第一个有效路径,它还将检查视图文件是否适用于所有其他显示模式未测试存在,因此可以将信息包含在缓存中(尽管仅针对该视图位置,而不是视图引擎中可用的所有位置)。另请注意,它如何将 lambda 传递给每个测试的显示模式以检查该模式的文件是否存在:
DisplayInfo displayInfoToCache = displayMode.GetDisplayInfo(
controllerContext.HttpContext,
virtualPath,
virtualPathExists: path => FileExists(controllerContext, path));
这就解释了为什么当你覆盖FileExists
它时,它也会被移动视图调用,即使它已经找到了非移动视图。
在任何情况下,可以像添加它们一样删除显示模式:通过在应用程序启动时更新 DisplayModes 集合。例如,删除移动显示模式,只保留默认的和非特定的模式(您无法清除集合,否则将永远找不到视图):
...
using System.Web.WebPages;
...
protected void Application_Start()
{
DisplayModeProvider.Instance.Modes.Remove(
DisplayModeProvider.Instance.Modes
.Single(m => m.DisplayModeId == "Mobile"));
答案很长,但希望它有意义!