Merge pull request #249 from pcc/xgetinputfocus
[mono.git] / mcs / class / System.Web.Mvc3 / Mvc / VirtualPathProviderViewEngine.cs
1 namespace System.Web.Mvc {
2     using System;
3     using System.Collections.Generic;
4     using System.Diagnostics.CodeAnalysis;
5     using System.Globalization;
6     using System.Linq;
7     using System.Web;
8     using System.Web.Hosting;
9     using System.Web.Mvc.Resources;
10
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];
18
19         private VirtualPathProvider _vpp;
20         internal Func<string, string> GetExtensionThunk = VirtualPathUtility.GetExtension;
21
22         [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
23         public string[] AreaMasterLocationFormats {
24             get;
25             set;
26         }
27
28         [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
29         public string[] AreaPartialViewLocationFormats {
30             get;
31             set;
32         }
33
34         [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
35         public string[] AreaViewLocationFormats {
36             get;
37             set;
38         }
39
40         [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
41         public string[] FileExtensions {
42             get;
43             set;
44         }
45
46         [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
47         public string[] MasterLocationFormats {
48             get;
49             set;
50         }
51
52         [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
53         public string[] PartialViewLocationFormats {
54             get;
55             set;
56         }
57
58         public IViewLocationCache ViewLocationCache {
59             get;
60             set;
61         }
62
63         [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
64         public string[] ViewLocationFormats {
65             get;
66             set;
67         }
68
69         protected VirtualPathProvider VirtualPathProvider {
70             get {
71                 if (_vpp == null) {
72                     _vpp = HostingEnvironment.VirtualPathProvider;
73                 }
74                 return _vpp;
75             }
76             set {
77                 _vpp = value;
78             }
79         }
80
81         protected VirtualPathProviderViewEngine() {
82             if (HttpContext.Current == null || HttpContext.Current.IsDebuggingEnabled) {
83                 ViewLocationCache = DefaultViewLocationCache.Null;
84             }
85             else {
86                 ViewLocationCache = new DefaultViewLocationCache();
87             }
88         }
89
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);
93         }
94
95         protected abstract IView CreatePartialView(ControllerContext controllerContext, string partialPath);
96
97         protected abstract IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath);
98
99         protected virtual bool FileExists(ControllerContext controllerContext, string virtualPath) {
100             return VirtualPathProvider.FileExists(virtualPath);
101         }
102
103         public virtual ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) {
104             if (controllerContext == null) {
105                 throw new ArgumentNullException("controllerContext");
106             }
107             if (String.IsNullOrEmpty(partialViewName)) {
108                 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "partialViewName");
109             }
110
111             string[] searched;
112             string controllerName = controllerContext.RouteData.GetRequiredString("controller");
113             string partialPath = GetPath(controllerContext, PartialViewLocationFormats, AreaPartialViewLocationFormats, "PartialViewLocationFormats", partialViewName, controllerName, _cacheKeyPrefix_Partial, useCache, out searched);
114
115             if (String.IsNullOrEmpty(partialPath)) {
116                 return new ViewEngineResult(searched);
117             }
118
119             return new ViewEngineResult(CreatePartialView(controllerContext, partialPath), this);
120         }
121
122         public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) {
123             if (controllerContext == null) {
124                 throw new ArgumentNullException("controllerContext");
125             }
126             if (String.IsNullOrEmpty(viewName)) {
127                 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewName");
128             }
129
130             string[] viewLocationsSearched;
131             string[] masterLocationsSearched;
132
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);
136
137             if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName))) {
138                 return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched));
139             }
140
141             return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
142         }
143
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;
146
147             if (String.IsNullOrEmpty(name)) {
148                 return String.Empty;
149             }
150
151             string areaName = AreaHelpers.GetAreaName(controllerContext.RouteData);
152             bool usingAreas = !String.IsNullOrEmpty(areaName);
153             List<ViewLocation> viewLocations = GetViewLocations(locations, (usingAreas) ? areaLocations : null);
154
155             if (viewLocations.Count == 0) {
156                 throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
157                     MvcResources.Common_PropertyCannotBeNullOrEmpty, locationsPropertyName));
158             }
159
160             bool nameRepresentsPath = IsSpecificPath(name);
161             string cacheKey = CreateCacheKey(cacheKeyPrefix, name, (nameRepresentsPath) ? String.Empty : controllerName, areaName);
162
163             if (useCache) {
164                 return ViewLocationCache.GetViewLocation(controllerContext.HttpContext, cacheKey);
165             }
166
167             return (nameRepresentsPath) ?
168                 GetPathFromSpecificName(controllerContext, name, cacheKey, ref searchedLocations) :
169                 GetPathFromGeneralName(controllerContext, viewLocations, name, controllerName, areaName, cacheKey, ref searchedLocations);
170         }
171
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];
175
176             for (int i = 0; i < locations.Count; i++) {
177                 ViewLocation location = locations[i];
178                 string virtualPath = location.Format(name, controllerName, areaName);
179
180                 if (FileExists(controllerContext, virtualPath)) {
181                     searchedLocations = _emptyLocations;
182                     result = virtualPath;
183                     ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, result);
184                     break;
185                 }
186
187                 searchedLocations[i] = virtualPath;
188             }
189
190             return result;
191         }
192
193         private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref string[] searchedLocations) {
194             string result = name;
195
196             if (!(FilePathIsSupported(name) && FileExists(controllerContext, name))) {
197                 result = String.Empty;
198                 searchedLocations = new[] { name };
199             }
200
201             ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, result);
202             return result;
203         }
204
205         private bool FilePathIsSupported(string virtualPath) {
206             if (FileExtensions == null) {
207                 // legacy behavior for custom ViewEngine that might not set the FileExtensions property
208                 return true;
209             }
210             else {
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);
214             }
215         }
216
217         private static List<ViewLocation> GetViewLocations(string[] viewLocationFormats, string[] areaViewLocationFormats) {
218             List<ViewLocation> allLocations = new List<ViewLocation>();
219
220             if (areaViewLocationFormats != null) {
221                 foreach (string areaViewLocationFormat in areaViewLocationFormats) {
222                     allLocations.Add(new AreaAwareViewLocation(areaViewLocationFormat));
223                 }
224             }
225
226             if (viewLocationFormats != null) {
227                 foreach (string viewLocationFormat in viewLocationFormats) {
228                     allLocations.Add(new ViewLocation(viewLocationFormat));
229                 }
230             }
231
232             return allLocations;
233         }
234
235         private static bool IsSpecificPath(string name) {
236             char c = name[0];
237             return (c == '~' || c == '/');
238         }
239
240         public virtual void ReleaseView(ControllerContext controllerContext, IView view) {
241             IDisposable disposable = view as IDisposable;
242             if (disposable != null) {
243                 disposable.Dispose();
244             }
245         }
246
247         private class ViewLocation {
248
249             protected string _virtualPathFormatString;
250
251             public ViewLocation(string virtualPathFormatString) {
252                 _virtualPathFormatString = virtualPathFormatString;
253             }
254
255             public virtual string Format(string viewName, string controllerName, string areaName) {
256                 return String.Format(CultureInfo.InvariantCulture, _virtualPathFormatString, viewName, controllerName);
257             }
258
259         }
260
261         private class AreaAwareViewLocation : ViewLocation {
262
263             public AreaAwareViewLocation(string virtualPathFormatString)
264                 : base(virtualPathFormatString) {
265             }
266
267             public override string Format(string viewName, string controllerName, string areaName) {
268                 return String.Format(CultureInfo.InvariantCulture, _virtualPathFormatString, viewName, controllerName, areaName);
269             }
270
271         }
272     }
273 }