1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle
.MonoRail
.Framework
18 using System
.Collections
.Generic
;
19 using System
.Threading
;
23 using Castle
.MonoRail
.Framework
.Container
;
24 using Castle
.MonoRail
.Framework
.Configuration
;
25 using Castle
.MonoRail
.Framework
.Descriptors
;
31 /// Coordinates the creation of new <see cref="MonoRailHttpHandler"/>
32 /// and uses the configuration to obtain the correct factories
35 public class MonoRailHttpHandlerFactory
: IHttpHandlerFactory
37 private static readonly string CurrentEngineContextKey
= "currentmrengineinstance";
38 private static readonly string CurrentControllerKey
= "currentmrcontroller";
39 private static readonly string CurrentControllerContextKey
= "currentmrcontrollercontext";
40 private readonly ReaderWriterLock locker
= new ReaderWriterLock();
42 private static IMonoRailConfiguration configuration
;
43 private static IMonoRailContainer mrContainer
;
44 private static IUrlTokenizer urlTokenizer
;
45 private static IEngineContextFactory engineContextFactory
;
46 private static IServiceProviderLocator serviceProviderLocator
;
47 private static IControllerFactory controllerFactory
;
48 private static IControllerContextFactory controllerContextFactory
;
49 private static IStaticResourceRegistry staticResourceRegistry
;
52 /// Initializes a new instance of the <see cref="MonoRailHttpHandlerFactory"/> class.
54 public MonoRailHttpHandlerFactory()
55 : this(ServiceProviderLocator
.Instance
)
60 /// Initializes a new instance of the <see cref="MonoRailHttpHandlerFactory"/> class.
62 /// <param name="serviceLocator">The service locator.</param>
63 public MonoRailHttpHandlerFactory(IServiceProviderLocator serviceLocator
)
65 serviceProviderLocator
= serviceLocator
;
69 /// Returns an instance of a class that implements
70 /// the <see cref="T:System.Web.IHttpHandler"></see> interface.
72 /// <param name="context">An instance of the <see cref="T:System.Web.HttpContext"></see> class that provides references to intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests.</param>
73 /// <param name="requestType">The HTTP data transfer method (GET or POST) that the client uses.</param>
74 /// <param name="url">The <see cref="P:System.Web.HttpRequest.RawUrl"></see> of the requested resource.</param>
75 /// <param name="pathTranslated">The <see cref="P:System.Web.HttpRequest.PhysicalApplicationPath"></see> to the requested resource.</param>
77 /// A new <see cref="T:System.Web.IHttpHandler"></see> object that processes the request.
79 public virtual IHttpHandler
GetHandler(HttpContext context
,
81 String url
, String pathTranslated
)
83 PerformOneTimeInitializationIfNecessary(context
);
87 HttpRequest req
= context
.Request
;
89 RouteMatch routeMatch
= (RouteMatch
)context
.Items
[RouteMatch
.RouteMatchKey
] ?? new RouteMatch();
91 UrlInfo urlInfo
= urlTokenizer
.TokenizeUrl(req
.FilePath
, req
.PathInfo
, req
.Url
, req
.IsLocal
, req
.ApplicationPath
);
93 if (urlInfo
.Area
== "MonoRail" && urlInfo
.Controller
== "Files")
95 return new ResourceFileHandler(urlInfo
, new DefaultStaticResourceRegistry());
98 // TODO: Identify requests for files (js files) and serve them directly bypassing the flow
100 IEngineContext engineContext
= engineContextFactory
.Create(mrContainer
, urlInfo
, context
, routeMatch
);
101 engineContext
.AddService(typeof(IEngineContext
), engineContext
);
102 context
.Items
[CurrentEngineContextKey
] = engineContext
;
104 IController controller
;
108 controller
= controllerFactory
.CreateController(urlInfo
.Area
, urlInfo
.Controller
);
110 catch(ControllerNotFoundException
)
112 return new NotFoundHandler(urlInfo
.Area
, urlInfo
.Controller
, engineContext
);
116 HttpResponse response
= context
.Response
;
118 if (response
.StatusCode
== 200)
120 response
.StatusCode
= 500;
123 engineContext
.LastException
= ex
;
125 engineContext
.Services
.ExtensionManager
.RaiseUnhandledError(engineContext
);
127 throw new MonoRailException("Error creating controller " + urlInfo
.Controller
, ex
);
130 ControllerMetaDescriptor controllerDesc
=
131 mrContainer
.ControllerDescriptorProvider
.BuildDescriptor(controller
);
133 IControllerContext controllerContext
=
134 controllerContextFactory
.Create(urlInfo
.Area
, urlInfo
.Controller
, urlInfo
.Action
, controllerDesc
, routeMatch
);
136 engineContext
.CurrentController
= controller
;
137 engineContext
.CurrentControllerContext
= controllerContext
;
139 context
.Items
[CurrentControllerKey
] = controller
;
140 context
.Items
[CurrentControllerContextKey
] = controllerContext
;
142 if (IsAsyncAction(controllerContext
) == false)
144 return CreateHandler(controllerDesc
, engineContext
, controller
, controllerContext
);
148 return CreateAsyncHandler(controllerDesc
, engineContext
, (IAsyncController
)controller
, controllerContext
);
153 /// Creates the handler.
155 /// <param name="controllerDesc">The controller descriptor.</param>
156 /// <param name="engineContext">The engine context.</param>
157 /// <param name="controller">The controller.</param>
158 /// <param name="controllerContext">The controller context.</param>
160 /// A new <see cref="T:System.Web.IHttpHandler"></see> object that processes the request.
162 protected virtual IHttpHandler
CreateHandler(ControllerMetaDescriptor controllerDesc
, IEngineContext engineContext
,
163 IController controller
, IControllerContext controllerContext
)
165 if (IgnoresSession(controllerDesc
.ControllerDescriptor
))
167 return new SessionlessMonoRailHttpHandler(engineContext
, controller
, controllerContext
);
169 return new MonoRailHttpHandler(engineContext
, controller
, controllerContext
);
174 /// Creates the handler.
176 /// <param name="controllerDesc">The controller descriptor.</param>
177 /// <param name="engineContext">The engine context.</param>
178 /// <param name="controller">The controller.</param>
179 /// <param name="controllerContext">The controller context.</param>
181 /// A new <see cref="T:System.Web.IHttpHandler"></see> object that processes the request.
183 protected virtual IHttpAsyncHandler
CreateAsyncHandler(ControllerMetaDescriptor controllerDesc
,
184 IEngineContext engineContext
, IAsyncController controller
,
185 IControllerContext controllerContext
)
187 if (IgnoresSession(controllerDesc
.ControllerDescriptor
))
189 return new AsyncSessionlessMonoRailHttpHandler(engineContext
, controller
, controllerContext
);
191 return new AsyncMonoRailHttpHandler(engineContext
, controller
, controllerContext
);
196 /// Enables a factory to reuse an existing handler instance.
198 /// <param name="handler">The <see cref="T:System.Web.IHttpHandler"></see> object to reuse.</param>
199 public virtual void ReleaseHandler(IHttpHandler handler
)
204 /// Resets the state (only used from test cases)
206 public void ResetState()
208 configuration
= null;
211 engineContextFactory
= null;
212 serviceProviderLocator
= null;
213 controllerFactory
= null;
214 controllerContextFactory
= null;
215 staticResourceRegistry
= null;
219 /// Gets or sets the configuration.
221 /// <value>The configuration.</value>
222 public IMonoRailConfiguration Configuration
224 get { return configuration; }
225 set { configuration = value; }
229 /// Gets or sets the container.
231 /// <value>The container.</value>
232 public IMonoRailContainer Container
234 get { return mrContainer; }
235 set { mrContainer = value; }
239 /// Gets or sets the service provider locator.
241 /// <value>The service provider locator.</value>
242 public IServiceProviderLocator ProviderLocator
244 get { return serviceProviderLocator; }
245 set { serviceProviderLocator = value; }
249 /// Gets or sets the URL tokenizer.
251 /// <value>The URL tokenizer.</value>
252 public IUrlTokenizer UrlTokenizer
254 get { return urlTokenizer; }
255 set { urlTokenizer = value; }
259 /// Gets or sets the engine context factory.
261 /// <value>The engine context factory.</value>
262 public IEngineContextFactory EngineContextFactory
264 get { return engineContextFactory; }
265 set { engineContextFactory = value; }
269 /// Gets or sets the controller factory.
271 /// <value>The controller factory.</value>
272 public IControllerFactory ControllerFactory
274 get { return controllerFactory; }
275 set { controllerFactory = value; }
279 /// Gets or sets the controller context factory.
281 /// <value>The controller context factory.</value>
282 public IControllerContextFactory ControllerContextFactory
284 get { return controllerContextFactory; }
285 set { controllerContextFactory = value; }
289 /// Checks whether we should ignore session for the specified controller.
291 /// <param name="controllerDesc">The controller desc.</param>
292 /// <returns></returns>
293 protected virtual bool IgnoresSession(ControllerDescriptor controllerDesc
)
295 return controllerDesc
.Sessionless
;
299 /// Checks whether the target action is an async method.
301 /// <param name="controllerContext">The controller context.</param>
302 /// <returns></returns>
303 protected virtual bool IsAsyncAction(IControllerContext controllerContext
)
305 if (controllerContext
.ControllerDescriptor
== null || controllerContext
.Action
== null)
309 return controllerContext
.ControllerDescriptor
.Actions
[controllerContext
.Action
] is AsyncActionPair
;
313 /// Creates the default service container.
315 /// <param name="userServiceProvider">The user service provider.</param>
316 /// <param name="appInstance">The app instance.</param>
317 /// <returns></returns>
318 protected virtual IMonoRailContainer
CreateDefaultMonoRailContainer(IServiceProviderEx userServiceProvider
,
319 HttpApplication appInstance
)
321 DefaultMonoRailContainer container
= new DefaultMonoRailContainer(userServiceProvider
);
323 container
.UseServicesFromParent();
324 container
.InstallPrimordialServices();
325 container
.Configure(Configuration
);
327 FireContainerCreated(appInstance
, container
);
329 // Too dependent on Http and MR surroundings services to be moved to Container class
330 if (!container
.HasService
<IServerUtility
>())
332 container
.AddService
<IServerUtility
>(new ServerUtilityAdapter(appInstance
.Context
.Server
));
334 if (!container
.HasService
<IRoutingEngine
>())
336 container
.AddService
<IRoutingEngine
>(RoutingModuleEx
.Engine
);
339 container
.InstallMissingServices();
340 container
.StartExtensionManager();
342 FireContainerInitialized(appInstance
, container
);
347 #region Static accessors
350 /// Gets the current engine context.
352 /// <value>The current engine context.</value>
353 public static IEngineContext CurrentEngineContext
357 if (HttpContext
.Current
== null)//for tests
359 return HttpContext
.Current
.Items
[CurrentEngineContextKey
] as IEngineContext
;
364 /// Gets the current controller.
366 /// <value>The current controller.</value>
367 public static IController CurrentController
369 get { return HttpContext.Current.Items[CurrentControllerKey] as IController; }
373 /// Gets the current controller context.
375 /// <value>The current controller context.</value>
376 public static IControllerContext CurrentControllerContext
378 get { return HttpContext.Current.Items[CurrentControllerContextKey] as IControllerContext; }
383 private void PerformOneTimeInitializationIfNecessary(HttpContext context
)
385 locker
.AcquireReaderLock(Timeout
.Infinite
);
387 if (mrContainer
!= null)
389 locker
.ReleaseReaderLock();
393 locker
.UpgradeToWriterLock(Timeout
.Infinite
);
395 if (mrContainer
!= null) // remember remember the race condition
397 locker
.ReleaseWriterLock();
403 if (configuration
== null)
405 configuration
= ObtainConfiguration(context
.ApplicationInstance
);
408 IServiceProviderEx userServiceProvider
= serviceProviderLocator
.LocateProvider();
410 mrContainer
= CreateDefaultMonoRailContainer(userServiceProvider
, context
.ApplicationInstance
);
414 locker
.ReleaseWriterLock();
418 private void FireContainerCreated(HttpApplication instance
, DefaultMonoRailContainer container
)
420 IMonoRailContainerEvents events
= instance
as IMonoRailContainerEvents
;
424 events
.Created(container
);
428 private void FireContainerInitialized(HttpApplication instance
, DefaultMonoRailContainer container
)
430 IMonoRailContainerEvents events
= instance
as IMonoRailContainerEvents
;
434 events
.Initialized(container
);
438 private IMonoRailConfiguration
ObtainConfiguration(HttpApplication appInstance
)
440 IMonoRailConfiguration config
= MonoRailConfiguration
.GetConfig();
442 IMonoRailConfigurationEvents events
= appInstance
as IMonoRailConfigurationEvents
;
446 config
= config
?? new MonoRailConfiguration();
448 events
.Configure(config
);
453 throw new ApplicationException("You have to provide a small configuration to use " +
454 "MonoRail. This can be done using the web.config or " +
455 "your global asax (your class that extends HttpApplication) " +
456 "through the method MonoRail_Configure(IMonoRailConfiguration config). " +
457 "Check the samples or the documentation.");
463 private void EnsureServices()
465 if (urlTokenizer
== null)
467 urlTokenizer
= mrContainer
.UrlTokenizer
;
469 if (engineContextFactory
== null)
471 engineContextFactory
= mrContainer
.EngineContextFactory
;
473 if (controllerFactory
== null)
475 controllerFactory
= mrContainer
.ControllerFactory
;
477 if (controllerContextFactory
== null)
479 controllerContextFactory
= mrContainer
.ControllerContextFactory
;
481 if (staticResourceRegistry
== null)
483 staticResourceRegistry
= mrContainer
.StaticResourceRegistry
;
488 /// Handles the controller not found situation
490 public class NotFoundHandler
: IHttpHandler
492 private readonly string area
;
493 private readonly string controller
;
494 private readonly IEngineContext engineContext
;
497 /// Initializes a new instance of the <see cref="NotFoundHandler"/> class.
499 /// <param name="area">The area.</param>
500 /// <param name="controller">The controller.</param>
501 /// <param name="engineContext">The engine context.</param>
502 public NotFoundHandler(string area
, string controller
, IEngineContext engineContext
)
505 this.controller
= controller
;
506 this.engineContext
= engineContext
;
510 /// Enables processing of HTTP Web requests by a custom HttpHandler that implements the <see cref="T:System.Web.IHttpHandler"/> interface.
512 /// <param name="context">An <see cref="T:System.Web.HttpContext"/> object that provides references to the intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests.</param>
513 public void ProcessRequest(HttpContext context
)
515 engineContext
.Response
.StatusCode
= 404;
516 engineContext
.Response
.StatusDescription
= "Not found";
518 if (engineContext
.Services
.ViewEngineManager
.HasTemplate("rescues/404"))
520 Dictionary
<string, object> parameters
= new Dictionary
<string, object>();
522 engineContext
.Services
.ViewEngineManager
.Process("rescues/404", null, engineContext
.Response
.Output
, parameters
);
524 return; // gracefully handled
527 throw new ControllerNotFoundException(area
, controller
);
531 /// Gets a value indicating whether another request can use the <see cref="T:System.Web.IHttpHandler"/> instance.
534 /// <returns>true if the <see cref="T:System.Web.IHttpHandler"/> instance is reusable; otherwise, false.</returns>
535 public bool IsReusable
537 get { return false; }