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