1 using System.Collections.Generic;
2 using System.Diagnostics;
3 using System.Globalization;
4 using System.Web.Compilation;
5 using System.Web.Hosting;
6 using System.Web.Routing;
7 using System.Web.UI;
8 
9 namespace System.Web.DynamicData {
10     /// <summary>
11     /// Route handler used for Dynamic Data
12     /// </summary>
13     public class DynamicDataRouteHandler : IRouteHandler {
14 
15         private static object s_requestContextKey = new object();
16         private static object s_metaTableKey = new object();
17 
18         private object _requestItemsKey = new object();
19 
20         /// <summary>
21         /// ctor
22         /// </summary>
DynamicDataRouteHandler()23         public DynamicDataRouteHandler() {
24             VirtualPathProvider = HostingEnvironment.VirtualPathProvider;
25             CreateHandlerCallback = delegate(string s) {
26                 return (Page)BuildManager.CreateInstanceFromVirtualPath(s, typeof(Page));
27             };
28         }
29 
30         /// <summary>
31         /// The MetaModel that the handler is associated with
32         /// </summary>
33         public MetaModel Model { get; internal set; }
34 
35         // the following properties are for mocking purposes
36         internal VirtualPathProvider VirtualPathProvider { get; set; }
37         private HttpContextBase _context;
38         internal HttpContextBase HttpContext {
39             get {
40                 return _context ?? new HttpContextWrapper(System.Web.HttpContext.Current);
41             }
42             set { _context = value; }
43         }
44         internal Func<string, IHttpHandler> CreateHandlerCallback { get; set; }
45 
46         /// <summary>
47         /// Create a handler to process a Dynamic Data request
48         /// </summary>
49         /// <param name="route">The Route that was matched</param>
50         /// <param name="table">The MetaTable found in the route</param>
51         /// <param name="action">The Action found in the route</param>
52         /// <returns></returns>
CreateHandler(DynamicDataRoute route, MetaTable table, string action)53         public virtual IHttpHandler CreateHandler(DynamicDataRoute route, MetaTable table, string action) {
54             // First, get the path to the page (could be custom, shared, or null)
55             string virtualPath = GetPageVirtualPath(route, table, action);
56 
57             if (virtualPath != null) {
58                 // Gets called only for custom pages that we know exist or templates that may or may not
59                 // exist. This method will throw if virtualPath does not exist, which is fine for templates
60                 // but is not fine for custom pages.
61                 return CreateHandlerCallback(virtualPath);
62             } else {
63                 // This should only occur in the event that scaffolding is disabled and the custom page
64                 // virtual path does not exist.
65                 return null;
66             }
67         }
68 
GetPageVirtualPath(DynamicDataRoute route, MetaTable table, string action)69         private string GetPageVirtualPath(DynamicDataRoute route, MetaTable table, string action) {
70             long cacheKey = Misc.CombineHashCodes(table, route.ViewName ?? action);
71 
72             Dictionary<long, string> virtualPathCache = GetVirtualPathCache();
73 
74             string virtualPath;
75             if (!virtualPathCache.TryGetValue(cacheKey, out virtualPath)) {
76                 virtualPath = GetPageVirtualPathNoCache(route, table, action);
77                 lock (virtualPathCache) {
78                     virtualPathCache[cacheKey] = virtualPath;
79                 }
80             }
81             return virtualPath;
82         }
83 
GetVirtualPathCache()84         private Dictionary<long, string> GetVirtualPathCache() {
85             var httpContext = HttpContext;
86             Dictionary<long, string> virtualPathCache = (Dictionary<long, string>)httpContext.Items[_requestItemsKey];
87             if (virtualPathCache == null) {
88                 virtualPathCache = new Dictionary<long, string>();
89                 httpContext.Items[_requestItemsKey] = virtualPathCache;
90             }
91             return virtualPathCache;
92         }
93 
GetPageVirtualPathNoCache(DynamicDataRoute route, MetaTable table, string action)94         private string GetPageVirtualPathNoCache(DynamicDataRoute route, MetaTable table, string action) {
95             // The view name defaults to the action
96             string viewName = route.ViewName ?? action;
97 
98             // First, get the path to the custom page
99             string customPageVirtualPath = GetCustomPageVirtualPath(table, viewName);
100 
101             if (VirtualPathProvider.FileExists(customPageVirtualPath)) {
102                 return customPageVirtualPath;
103             } else {
104                 if (table.Scaffold) {
105                     // If it doesn't exist, try the scaffolded page, but only if scaffolding is enabled on this table
106                     return GetScaffoldPageVirtualPath(table, viewName);
107                 } else {
108                     // If scaffolding is disabled, null the path so BuildManager doesn't get called.
109                     return null;
110                 }
111             }
112         }
113 
114         /// <summary>
115         /// Build the path to a custom page. By default, it looks like ~/DynamicData/CustomPages/[tablename]/[viewname].aspx
116         /// </summary>
117         /// <param name="table">The MetaTable that the page is for</param>
118         /// <param name="viewName">The view name</param>
119         /// <returns></returns>
GetCustomPageVirtualPath(MetaTable table, string viewName)120         protected virtual string GetCustomPageVirtualPath(MetaTable table, string viewName) {
121             string pathPattern = "{0}CustomPages/{1}/{2}.aspx";
122             return String.Format(CultureInfo.InvariantCulture, pathPattern, Model.DynamicDataFolderVirtualPath, table.Name, viewName);
123         }
124 
125         /// <summary>
126         /// Build the path to a page template. By default, it looks like ~/DynamicData/PageTemplates/[tablename]/[viewname].aspx
127         /// </summary>
128         /// <param name="table">The MetaTable that the page is for</param>
129         /// <param name="viewName">The view name</param>
130         /// <returns></returns>
GetScaffoldPageVirtualPath(MetaTable table, string viewName)131         protected virtual string GetScaffoldPageVirtualPath(MetaTable table, string viewName) {
132             string pathPattern = "{0}PageTemplates/{1}.aspx";
133             return String.Format(CultureInfo.InvariantCulture, pathPattern, Model.DynamicDataFolderVirtualPath, viewName);
134         }
135 
136         /// <summary>
137         /// Return the RequestContext for this request. A new one is created if needed (can happen if the current request
138         /// is not a Dynamic Data request)
139         /// </summary>
140         /// <param name="httpContext">The current HttpContext</param>
141         /// <returns>The RequestContext</returns>
GetRequestContext(HttpContext httpContext)142         public static RequestContext GetRequestContext(HttpContext httpContext) {
143             if (httpContext == null) {
144                 throw new ArgumentNullException("httpContext");
145             }
146 
147             return GetRequestContext(new HttpContextWrapper(httpContext));
148         }
149 
GetRequestContext(HttpContextBase httpContext)150         internal static RequestContext GetRequestContext(HttpContextBase httpContext) {
151             Debug.Assert(httpContext != null);
152 
153             // Look for the RequestContext in the HttpContext
154             var requestContext = httpContext.Items[s_requestContextKey] as RequestContext;
155 
156             // If the current request didn't go through the routing engine (e.g. normal page),
157             // there won't be a RequestContext.  If so, create a new one and save it
158             if (requestContext == null) {
159                 var routeData = new RouteData();
160                 requestContext = new RequestContext(httpContext, routeData);
161 
162                 // Add the query string params to the route data.  This allows non routed pages to support filtering.
163                 DynamicDataRoute.AddQueryStringParamsToRouteData(httpContext, routeData);
164 
165                 httpContext.Items[s_requestContextKey] = requestContext;
166             }
167 
168             return requestContext;
169         }
170 
171         /// <summary>
172         /// The MetaTable associated with the current HttpRequest. Can be null for non-Dynamic Data requests.
173         /// </summary>
174         /// <param name="httpContext">The current HttpContext</param>
GetRequestMetaTable(HttpContext httpContext)175         public static MetaTable GetRequestMetaTable(HttpContext httpContext) {
176             if (httpContext == null) {
177                 throw new ArgumentNullException("httpContext");
178             }
179 
180             return GetRequestMetaTable(new HttpContextWrapper(httpContext));
181         }
182 
GetRequestMetaTable(HttpContextBase httpContext)183         internal static MetaTable GetRequestMetaTable(HttpContextBase httpContext) {
184             Debug.Assert(httpContext != null);
185 
186             return (MetaTable)httpContext.Items[s_metaTableKey];
187         }
188 
189         /// <summary>
190         /// Set the MetaTable associated with the current HttpRequest.  Normally, this is set automatically from the
191         /// route, but this method is useful to set the table when used outside of routing.
192         /// </summary>
SetRequestMetaTable(HttpContext httpContext, MetaTable table)193         public static void SetRequestMetaTable(HttpContext httpContext, MetaTable table) {
194             SetRequestMetaTable(new HttpContextWrapper(httpContext), table);
195         }
196 
SetRequestMetaTable(HttpContextBase httpContext, MetaTable table)197         internal static void SetRequestMetaTable(HttpContextBase httpContext, MetaTable table) {
198             Debug.Assert(httpContext != null);
199 
200             httpContext.Items[s_metaTableKey] = table;
201         }
202 
203         #region IRouteHandler Members
IRouteHandler.GetHttpHandler(RequestContext requestContext)204         IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext) {
205             // Save the RequestContext
206             Debug.Assert(requestContext.HttpContext.Items[s_requestContextKey] == null);
207             requestContext.HttpContext.Items[s_requestContextKey] = requestContext;
208 
209             // Get the dynamic route
210             var route = (DynamicDataRoute)requestContext.RouteData.Route;
211 
212             // Get the Model from the route
213             MetaModel model = route.Model;
214 
215             // Get the MetaTable and save it in the HttpContext
216             MetaTable table = route.GetTableFromRouteData(requestContext.RouteData);
217             requestContext.HttpContext.Items[s_metaTableKey] = table;
218 
219             // Get the action from the request context
220             string action = route.GetActionFromRouteData(requestContext.RouteData);
221 
222             return CreateHandler(route, table, action);
223         }
224         #endregion
225     }
226 }
227