
// 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);
}
}
}
}
