2009-07-11 Michael Barker <mike@middlesoft.co.uk>
[mono.git] / mcs / class / System.Web.DynamicData / System.Web.DynamicData / DynamicDataRouteHandler.cs
1 //
2 // DynamicDataRouteHandler.cs
3 //
4 // Authors:
5 //      Atsushi Enomoto <atsushi@ximian.com>
6 //      Marek Habersack <mhabersack@novell.com>
7 //
8 // Copyright (C) 2008-2009 Novell Inc. http://novell.com
9 //
10
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31 using System;
32 using System.Collections;
33 using System.Collections.Generic;
34 using System.Collections.Specialized;
35 using System.Data.Linq.Mapping;
36 using System.Globalization;
37 using System.Security.Permissions;
38 using System.Security.Principal;
39 using System.Threading;
40 using System.Web.Caching;
41 using System.Web.Compilation;
42 using System.Web.Hosting;
43 using System.Web.Routing;
44 using System.Web.UI;
45
46 namespace System.Web.DynamicData
47 {
48         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
49         [AspNetHostingPermission (SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
50         public class DynamicDataRouteHandler : IRouteHandler
51         {
52                 static ReaderWriterLockSlim contextsLock = new ReaderWriterLockSlim ();
53                 
54                 static Dictionary <HttpContext, RouteContext> contexts = new Dictionary <HttpContext, RouteContext> ();
55                 Dictionary <RouteContext, IHttpHandler> handlers;
56
57                 Dictionary <RouteContext, IHttpHandler> Handlers {
58                         get {
59                                 if (handlers == null)
60                                         handlers = new Dictionary <RouteContext, IHttpHandler> ();
61
62                                 return handlers;
63                         }
64                 }
65
66                 static RouteContext GetOrCreateRouteContext (HttpContext httpContext)
67                 {
68                         RouteContext rc = null;
69                         bool locked = false;
70                         try {
71                                 contextsLock.EnterReadLock ();
72                                 locked = true;
73                                 if (contexts.TryGetValue (httpContext, out rc) && rc != null)
74                                         return rc;
75                         } finally {
76                                 if (locked)
77                                         contextsLock.ExitReadLock ();
78                         }
79
80                         locked = false;
81                         try {
82                                 contextsLock.EnterWriteLock ();
83                                 locked = true;
84                                 rc = MakeRouteContext (new RequestContext (new HttpContextWrapper (httpContext), new RouteData ()), null, null, null);
85                                 contexts.Add (httpContext, rc);
86                         } finally {
87                                 if (locked)
88                                         contextsLock.ExitWriteLock ();
89                         }
90
91                         return rc;
92                 }
93                 
94                 public static RequestContext GetRequestContext (HttpContext httpContext)
95                 {
96                         if (httpContext == null)
97                                 throw new ArgumentNullException ("httpContext");
98                         
99                         return GetOrCreateRouteContext (httpContext).Context;
100                 }
101
102                 public static MetaTable GetRequestMetaTable (HttpContext httpContext)
103                 {
104                         if (httpContext == null)
105                                 throw new ArgumentNullException ("httpContext");
106
107                         RouteContext rc;
108                         bool locked = false;
109                         try {
110                                 contextsLock.EnterReadLock ();
111                                 locked = true;
112                                 if (contexts.TryGetValue (httpContext, out rc) && rc != null)
113                                         return rc.Table;
114                         } finally {
115                                 if (locked)
116                                         contextsLock.ExitReadLock ();
117                         }
118
119                         return null;
120                 }
121
122                 public static void SetRequestMetaTable (HttpContext httpContext, MetaTable table)
123                 {
124                         // And tradiationally... some .NET emulation code
125                         if (httpContext == null)
126                                 throw new NullReferenceException ();
127
128                         GetOrCreateRouteContext (httpContext).Table = table;
129                 }
130
131                 public DynamicDataRouteHandler ()
132                 {
133                 }
134
135                 public MetaModel Model { get; internal set; }
136
137                 [MonoTODO ("Needs a working test")]
138                 public virtual IHttpHandler CreateHandler (DynamicDataRoute route, MetaTable table, string action)
139                 {
140                         // .NET bug emulation mode
141                         if (route == null || table == null || action == null)
142                                 throw new NullReferenceException ();
143
144                         // NOTE: all code below is a result of guessing as no tests succeed for this
145                         // call so far!
146
147                         IHttpHandler ret = null;
148                         
149                         // Give custom pages a chance
150                         string viewName = String.IsNullOrEmpty (action) ? route.ViewName : action;
151                         string path = GetCustomPageVirtualPath (table, viewName);
152
153                         // Pages might be in app resources, need to use a VPP
154                         VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider;
155                         
156                         if (vpp != null && vpp.FileExists (path))
157                                 ret = BuildManager.CreateInstanceFromVirtualPath (path, typeof (Page)) as IHttpHandler;
158
159                         if (ret != null)
160                                 return ret;
161
162                         path = GetScaffoldPageVirtualPath (table, viewName);
163                         if (vpp != null && vpp.FileExists (path))
164                                 ret = BuildManager.CreateInstanceFromVirtualPath (path, typeof (Page)) as IHttpHandler;
165                         
166                         return ret;
167                 }
168
169                 protected virtual string GetCustomPageVirtualPath (MetaTable table, string viewName)
170                 {
171                         // No such checks are made in .NET, we won't follow the pattern...
172                         MetaModel model = Model;
173                         if (table == null || model == null)
174                                 throw new NullReferenceException (); // yuck
175
176                         // Believe it or not, this is what .NET does - pass a null/empty viewName
177                         // and you get /.aspx at the end...
178                         return model.DynamicDataFolderVirtualPath + "CustomPages/" + table.Name + "/" + viewName + ".aspx";
179                 }
180
181                 protected virtual string GetScaffoldPageVirtualPath (MetaTable table, string viewName)
182                 {
183                         // No such checks are made in .NET, we won't follow the pattern...
184                         MetaModel model = Model;
185                         if (table == null || model == null)
186                                 throw new NullReferenceException (); // yuck
187
188                         // Believe it or not, this is what .NET does - pass a null/empty viewName
189                         // and you get /.aspx at the end...
190                         return model.DynamicDataFolderVirtualPath + "PageTemplates/" + viewName + ".aspx";
191                 }
192
193                 IHttpHandler IRouteHandler.GetHttpHandler (RequestContext requestContext)
194                 {
195                         if (requestContext == null)
196                                 throw new ArgumentNullException ("requestContext");
197                         RouteData rd = requestContext.RouteData;
198                         var dr = rd.Route as DynamicDataRoute;
199                         if (dr == null)
200                                 throw new ArgumentException ("The argument RequestContext does not have DynamicDataRoute in its RouteData");
201                         string action = dr.GetActionFromRouteData (rd);
202                         MetaTable mt = dr.GetTableFromRouteData (rd);
203                         RouteContext rc = MakeRouteContext (requestContext, dr, action, mt);
204                         IHttpHandler h;
205                         
206                         Dictionary <RouteContext, IHttpHandler> handlers = Handlers;
207                         if (handlers.TryGetValue (rc, out h))
208                                 return h;
209                         h = CreateHandler (dr, mt, action);
210                         handlers.Add (rc, h);
211                         return h;
212                 }
213
214                 static RouteContext MakeRouteContext (RequestContext context, DynamicDataRoute route, string action, MetaTable table)
215                 {
216                         RouteData rd = null;
217                         
218                         if (route == null) {
219                                 rd = context.RouteData;
220                                 route = rd.Route as DynamicDataRoute;
221                         }
222
223                         if (route != null) {
224                                 if (action == null) {
225                                         if (rd == null)
226                                                 rd = context.RouteData;
227                                         action = route.GetActionFromRouteData (rd);
228                                 }
229                         
230                                 if (table == null) {
231                                         if (rd == null)
232                                                 rd = context.RouteData;
233                                 
234                                         table = route.GetTableFromRouteData (rd);
235                                 }
236                         }
237                         
238                         return new RouteContext () {
239                                 Route = route,
240                                 Action = action,
241                                 Table = table,
242                                 Context = context};
243                 }
244                 
245                 sealed class RouteContext
246                 {
247                         public DynamicDataRoute Route;
248                         public string Action;
249                         public MetaTable Table;
250                         public RequestContext Context;
251
252                         public RouteContext ()
253                         {
254                         }
255                         
256                         public override bool Equals (object obj)
257                         {
258                                 RouteContext other = obj as RouteContext;
259                                 return other.Route == Route & other.Action == Action && other.Table == Table && other.Context == Context;
260                         }
261
262                         public override int GetHashCode ()
263                         {
264                                 return (Route != null ? Route.GetHashCode () << 27 : 0) +
265                                         (Action != null ? Action.GetHashCode () << 19 : 0) +
266                                         (Table != null ? Table.GetHashCode () << 9 : 0) +
267                                         (Context != null ? Context.GetHashCode () : 0);
268                         }
269                 }
270         }
271 }