1 namespace System.Web.Mvc {
3 using System.Collections.Generic;
4 using System.Diagnostics.CodeAnalysis;
5 using System.Globalization;
8 using System.Web.Hosting;
9 using System.Web.Mvc.Resources;
11 public abstract class VirtualPathProviderViewEngine : IViewEngine {
12 // format is ":ViewCacheEntry:{cacheType}:{prefix}:{name}:{controllerName}:{areaName}:"
13 private const string _cacheKeyFormat = ":ViewCacheEntry:{0}:{1}:{2}:{3}:{4}:";
14 private const string _cacheKeyPrefix_Master = "Master";
15 private const string _cacheKeyPrefix_Partial = "Partial";
16 private const string _cacheKeyPrefix_View = "View";
17 private static readonly string[] _emptyLocations = new string[0];
19 private VirtualPathProvider _vpp;
20 internal Func<string, string> GetExtensionThunk = VirtualPathUtility.GetExtension;
22 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
23 public string[] AreaMasterLocationFormats {
28 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
29 public string[] AreaPartialViewLocationFormats {
34 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
35 public string[] AreaViewLocationFormats {
40 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
41 public string[] FileExtensions {
46 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
47 public string[] MasterLocationFormats {
52 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
53 public string[] PartialViewLocationFormats {
58 public IViewLocationCache ViewLocationCache {
63 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
64 public string[] ViewLocationFormats {
69 protected VirtualPathProvider VirtualPathProvider {
72 _vpp = HostingEnvironment.VirtualPathProvider;
81 protected VirtualPathProviderViewEngine() {
82 if (HttpContext.Current == null || HttpContext.Current.IsDebuggingEnabled) {
83 ViewLocationCache = DefaultViewLocationCache.Null;
86 ViewLocationCache = new DefaultViewLocationCache();
90 private string CreateCacheKey(string prefix, string name, string controllerName, string areaName) {
91 return String.Format(CultureInfo.InvariantCulture, _cacheKeyFormat,
92 GetType().AssemblyQualifiedName, prefix, name, controllerName, areaName);
95 protected abstract IView CreatePartialView(ControllerContext controllerContext, string partialPath);
97 protected abstract IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath);
99 protected virtual bool FileExists(ControllerContext controllerContext, string virtualPath) {
100 return VirtualPathProvider.FileExists(virtualPath);
103 public virtual ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) {
104 if (controllerContext == null) {
105 throw new ArgumentNullException("controllerContext");
107 if (String.IsNullOrEmpty(partialViewName)) {
108 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "partialViewName");
112 string controllerName = controllerContext.RouteData.GetRequiredString("controller");
113 string partialPath = GetPath(controllerContext, PartialViewLocationFormats, AreaPartialViewLocationFormats, "PartialViewLocationFormats", partialViewName, controllerName, _cacheKeyPrefix_Partial, useCache, out searched);
115 if (String.IsNullOrEmpty(partialPath)) {
116 return new ViewEngineResult(searched);
119 return new ViewEngineResult(CreatePartialView(controllerContext, partialPath), this);
122 public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) {
123 if (controllerContext == null) {
124 throw new ArgumentNullException("controllerContext");
126 if (String.IsNullOrEmpty(viewName)) {
127 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewName");
130 string[] viewLocationsSearched;
131 string[] masterLocationsSearched;
133 string controllerName = controllerContext.RouteData.GetRequiredString("controller");
134 string viewPath = GetPath(controllerContext, ViewLocationFormats, AreaViewLocationFormats, "ViewLocationFormats", viewName, controllerName, _cacheKeyPrefix_View, useCache, out viewLocationsSearched);
135 string masterPath = GetPath(controllerContext, MasterLocationFormats, AreaMasterLocationFormats, "MasterLocationFormats", masterName, controllerName, _cacheKeyPrefix_Master, useCache, out masterLocationsSearched);
137 if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName))) {
138 return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched));
141 return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
144 private string GetPath(ControllerContext controllerContext, string[] locations, string[] areaLocations, string locationsPropertyName, string name, string controllerName, string cacheKeyPrefix, bool useCache, out string[] searchedLocations) {
145 searchedLocations = _emptyLocations;
147 if (String.IsNullOrEmpty(name)) {
151 string areaName = AreaHelpers.GetAreaName(controllerContext.RouteData);
152 bool usingAreas = !String.IsNullOrEmpty(areaName);
153 List<ViewLocation> viewLocations = GetViewLocations(locations, (usingAreas) ? areaLocations : null);
155 if (viewLocations.Count == 0) {
156 throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
157 MvcResources.Common_PropertyCannotBeNullOrEmpty, locationsPropertyName));
160 bool nameRepresentsPath = IsSpecificPath(name);
161 string cacheKey = CreateCacheKey(cacheKeyPrefix, name, (nameRepresentsPath) ? String.Empty : controllerName, areaName);
164 return ViewLocationCache.GetViewLocation(controllerContext.HttpContext, cacheKey);
167 return (nameRepresentsPath) ?
168 GetPathFromSpecificName(controllerContext, name, cacheKey, ref searchedLocations) :
169 GetPathFromGeneralName(controllerContext, viewLocations, name, controllerName, areaName, cacheKey, ref searchedLocations);
172 private string GetPathFromGeneralName(ControllerContext controllerContext, List<ViewLocation> locations, string name, string controllerName, string areaName, string cacheKey, ref string[] searchedLocations) {
173 string result = String.Empty;
174 searchedLocations = new string[locations.Count];
176 for (int i = 0; i < locations.Count; i++) {
177 ViewLocation location = locations[i];
178 string virtualPath = location.Format(name, controllerName, areaName);
180 if (FileExists(controllerContext, virtualPath)) {
181 searchedLocations = _emptyLocations;
182 result = virtualPath;
183 ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, result);
187 searchedLocations[i] = virtualPath;
193 private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref string[] searchedLocations) {
194 string result = name;
196 if (!(FilePathIsSupported(name) && FileExists(controllerContext, name))) {
197 result = String.Empty;
198 searchedLocations = new[] { name };
201 ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, result);
205 private bool FilePathIsSupported(string virtualPath) {
206 if (FileExtensions == null) {
207 // legacy behavior for custom ViewEngine that might not set the FileExtensions property
211 // get rid of the '.' because the FileExtensions property expects extensions withouth a dot.
212 string extension = GetExtensionThunk(virtualPath).TrimStart('.');
213 return FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
217 private static List<ViewLocation> GetViewLocations(string[] viewLocationFormats, string[] areaViewLocationFormats) {
218 List<ViewLocation> allLocations = new List<ViewLocation>();
220 if (areaViewLocationFormats != null) {
221 foreach (string areaViewLocationFormat in areaViewLocationFormats) {
222 allLocations.Add(new AreaAwareViewLocation(areaViewLocationFormat));
226 if (viewLocationFormats != null) {
227 foreach (string viewLocationFormat in viewLocationFormats) {
228 allLocations.Add(new ViewLocation(viewLocationFormat));
235 private static bool IsSpecificPath(string name) {
237 return (c == '~' || c == '/');
240 public virtual void ReleaseView(ControllerContext controllerContext, IView view) {
241 IDisposable disposable = view as IDisposable;
242 if (disposable != null) {
243 disposable.Dispose();
247 private class ViewLocation {
249 protected string _virtualPathFormatString;
251 public ViewLocation(string virtualPathFormatString) {
252 _virtualPathFormatString = virtualPathFormatString;
255 public virtual string Format(string viewName, string controllerName, string areaName) {
256 return String.Format(CultureInfo.InvariantCulture, _virtualPathFormatString, viewName, controllerName);
261 private class AreaAwareViewLocation : ViewLocation {
263 public AreaAwareViewLocation(string virtualPathFormatString)
264 : base(virtualPathFormatString) {
267 public override string Format(string viewName, string controllerName, string areaName) {
268 return String.Format(CultureInfo.InvariantCulture, _virtualPathFormatString, viewName, controllerName, areaName);