More working tests.
[castle.git] / MonoRail / Castle.MonoRail.Framework / Controller.cs
blob61671b69a51dc8874ebb892d949ce35c6193db3b
1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
2 //
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
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
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
17 using System;
18 using System.Collections;
19 using System.Collections.Generic;
20 using System.Collections.Specialized;
21 using System.IO;
22 using System.Reflection;
23 using System.Web;
24 using Castle.Components.Binder;
25 using Castle.Components.Common.EmailSender;
26 using Castle.Components.Validator;
27 using Castle.Core.Logging;
28 using Castle.MonoRail.Framework.Descriptors;
29 using Castle.MonoRail.Framework.Resources;
30 using Castle.MonoRail.Framework.Internal;
31 using Core;
32 using Helpers;
34 /// <summary>
35 /// Implements the core functionality and exposes the
36 /// common methods for concrete controllers.
37 /// </summary>
38 public abstract class Controller : IController, IValidatorAccessor, IRedirectSupport
40 private IEngineContext engineContext;
41 private IControllerContext context;
42 private ILogger logger = NullLogger.Instance;
43 private bool directRenderInvoked;
45 private IHelperFactory helperFactory;
46 private IServiceInitializer serviceInitializer;
47 private IFilterFactory filterFactory;
48 private IViewEngineManager viewEngineManager;
49 private IActionSelector actionSelector;
50 private IScaffoldingSupport scaffoldSupport;
51 private FilterDescriptor[] filters = new FilterDescriptor[0];
52 private ValidatorRunner validatorRunner;
53 private Dictionary<object, ErrorSummary> validationSummaryPerInstance;
54 private Dictionary<object, ErrorList> boundInstances;
56 #region IController
58 /// <summary>
59 /// Occurs just before the action execution.
60 /// </summary>
61 public event ControllerHandler BeforeAction;
63 /// <summary>
64 /// Occurs just after the action execution.
65 /// </summary>
66 public event ControllerHandler AfterAction;
68 /// <summary>
69 /// Performs the specified action, which means:
70 /// <br/>
71 /// 1. Define the default view name<br/>
72 /// 2. Run the before filters<br/>
73 /// 3. Select the method related to the action name and invoke it<br/>
74 /// 4. On error, execute the rescues if available<br/>
75 /// 5. Run the after filters<br/>
76 /// 6. Invoke the view engine<br/>
77 /// </summary>
78 /// <param name="engineContext">The engine context.</param>
79 /// <param name="context">The controller context.</param>
80 public virtual void Process(IEngineContext engineContext, IControllerContext context)
82 this.context = context;
83 SetEngineContext(engineContext);
85 ResolveLayout();
86 CreateAndInitializeHelpers();
87 CreateFiltersDescriptors();
88 ProcessScaffoldIfAvailable();
89 ActionProviderUtil.RegisterActions(engineContext, this, context);
90 Initialize();
91 RunActionAndRenderView();
94 /// <summary>
95 /// Invoked by the view engine to perform
96 /// any logic before the view is sent to the client.
97 /// </summary>
98 /// <param name="view"></param>
99 public virtual void PreSendView(object view)
103 /// <summary>
104 /// Invoked by the view engine to perform
105 /// any logic after the view had been sent to the client.
106 /// </summary>
107 /// <param name="view"></param>
108 public virtual void PostSendView(object view)
112 /// <summary>
113 /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
114 /// </summary>
115 public virtual void Dispose()
117 DisposeFilters();
120 #endregion
122 #region IValidatorAccessor
124 /// <summary>
125 /// Gets the validator runner instance.
126 /// </summary>
127 /// <value>The validator instance.</value>
128 public ValidatorRunner Validator
132 if (validatorRunner == null)
134 validatorRunner = CreateValidatorRunner(engineContext.Services.ValidatorRegistry);
136 return validatorRunner;
138 set { validatorRunner = value; }
141 /// <summary>
142 /// Gets the bound instance errors. These are errors relative to
143 /// the binding process performed for the specified instance.
144 /// </summary>
145 /// <value>The bound instance errors.</value>
146 public IDictionary<object, ErrorList> BoundInstanceErrors
150 if (boundInstances == null)
152 boundInstances = new Dictionary<object, ErrorList>();
154 return boundInstances;
158 /// <summary>
159 /// Populates the validator error summary with errors relative to the
160 /// validation rules associated with the target type.
161 /// </summary>
162 /// <param name="instance">The instance.</param>
163 /// <param name="binderUsedForBinding">The binder used for binding.</param>
164 public void PopulateValidatorErrorSummary(object instance, ErrorSummary binderUsedForBinding)
166 if (validationSummaryPerInstance == null)
168 validationSummaryPerInstance = new Dictionary<object, ErrorSummary>();
170 validationSummaryPerInstance[instance] = binderUsedForBinding;
173 /// <summary>
174 /// Gets the error summary associated with validation errors.
175 /// <para>
176 /// Will only work for instances populated by the <c>DataBinder</c>
177 /// </para>
178 /// </summary>
179 /// <param name="instance">object instance</param>
180 /// <returns>Error summary instance (can be null if the DataBinder wasn't configured to validate)</returns>
181 protected ErrorSummary GetErrorSummary(object instance)
183 if (validationSummaryPerInstance == null)
185 return null;
187 return validationSummaryPerInstance.ContainsKey(instance) ? validationSummaryPerInstance[instance] : null;
190 /// <summary>
191 /// Returns <c>true</c> if the given instance had
192 /// validation errors during binding.
193 /// <para>
194 /// Will only work for instances populated by the <c>DataBinder</c>
195 /// </para>
196 /// </summary>
197 /// <param name="instance">object instance</param>
198 /// <returns><c>true</c> if the validation had an error</returns>
199 protected bool HasValidationError(object instance)
201 ErrorSummary summary = GetErrorSummary(instance);
203 if (summary == null)
205 return false;
208 return summary.ErrorsCount != 0;
211 /// <summary>
212 /// Gets a list of errors that were thrown during the
213 /// object process, like conversion errors.
214 /// </summary>
215 /// <param name="instance">The instance that was populated by a binder.</param>
216 /// <returns>List of errors</returns>
217 protected ErrorList GetDataBindErrors(object instance)
219 if (boundInstances != null && boundInstances.ContainsKey(instance))
221 return boundInstances[instance];
223 return null;
226 #endregion
228 #region Useful Properties
230 /// <summary>
231 /// Gets the controller context.
232 /// </summary>
233 /// <value>The controller context.</value>
234 public IControllerContext ControllerContext
236 get { return context; }
237 set { context = value; }
240 /// <summary>
241 /// Gets the view folder -- (areaname +
242 /// controllername) or just controller name -- that this controller
243 /// will use by default.
244 /// </summary>
245 public string ViewFolder
247 get { return context.ViewFolder; }
248 set { context.ViewFolder = value; }
251 /// <summary>
252 /// This is intended to be used by MonoRail infrastructure.
253 /// </summary>
254 public ControllerMetaDescriptor MetaDescriptor
256 get { return context.ControllerDescriptor; }
257 set { context.ControllerDescriptor = value; }
260 /// <summary>
261 /// Gets the actions available in this controller.
262 /// </summary>
263 /// <remarks>It is supposed to be used by MonoRail infrastructure only</remarks>
264 /// <value>The actions.</value>
265 public ICollection Actions
267 get { return MetaDescriptor.Actions.Values; }
270 /// <summary>
271 /// Gets a dicitionary of name/<see cref="IResource"/>
272 /// </summary>
273 /// <remarks>It is supposed to be used by MonoRail infrastructure only</remarks>
274 /// <value>The resources.</value>
275 public IDictionary<string, IResource> Resources
277 get { return context.Resources; }
280 /// <summary>
281 /// Gets a dictionary of name/helper instance
282 /// </summary>
283 /// <value>The helpers.</value>
284 public IDictionary Helpers
286 get { return context.Helpers; }
289 /// <summary>
290 /// Gets a value indicating whether the request is a post.
291 /// </summary>
292 /// <value>
293 /// <see langword="true"/> if this request is a post; otherwise, <see langword="false"/>.
294 /// </value>
295 public bool IsPost
297 get { return engineContext.Request.HttpMethod == "POST"; }
300 /// <summary>
301 /// Gets a value indicating whether the request is a get.
302 /// </summary>
303 /// <value>
304 /// <see langword="true"/> if this request is a get; otherwise, <see langword="false"/>.
305 /// </value>
306 public bool IsGet
308 get { return engineContext.Request.HttpMethod == "GET"; }
311 /// <summary>
312 /// Gets a value indicating whether the request is a put.
313 /// </summary>
314 /// <value>
315 /// <see langword="true"/> if this request is a put; otherwise, <see langword="false"/>.
316 /// </value>
317 public bool IsPut
319 get { return engineContext.Request.HttpMethod == "PUT"; }
322 /// <summary>
323 /// Gets a value indicating whether the request is a head.
324 /// </summary>
325 /// <value>
326 /// <see langword="true"/> if this request is a head; otherwise, <see langword="false"/>.
327 /// </value>
328 public bool IsHead
330 get { return engineContext.Request.HttpMethod == "HEAD"; }
333 /// <summary>
334 /// Gets the controller's name.
335 /// </summary>
336 public string Name
338 get { return context.Name; }
341 /// <summary>
342 /// Gets the controller's area name.
343 /// </summary>
344 public string AreaName
346 get { return context.AreaName; }
349 /// <summary>
350 /// Gets or set the layout being used.
351 /// </summary>
352 public string LayoutName
354 get { return (context.LayoutNames != null && context.LayoutNames.Length != 0) ? context.LayoutNames[0] : null; }
357 if (value == null)
359 context.LayoutNames = null;
361 else
363 context.LayoutNames = new string[] { value };
368 /// <summary>
369 /// Gets or set the layouts being used.
370 /// </summary>
371 public string[] LayoutNames
373 get { return context.LayoutNames; }
374 set { context.LayoutNames = value; }
377 /// <summary>
378 /// Gets the name of the action being processed.
379 /// </summary>
380 public string Action
382 get { return context.Action; }
385 /// <summary>
386 /// Logger for the controller
387 /// </summary>
388 public ILogger Logger
390 get { return logger; }
391 set { logger = value; }
394 /// <summary>
395 /// Gets or sets the view which will be rendered by this action.
396 /// </summary>
397 public string SelectedViewName
399 get { return context.SelectedViewName; }
400 set { context.SelectedViewName = value; }
403 /// <summary>
404 /// Gets the property bag, which is used
405 /// to pass variables to the view.
406 /// </summary>
407 public IDictionary PropertyBag
409 get { return context.PropertyBag; }
410 set { context.PropertyBag = value; }
413 /// <summary>
414 /// Gets the context of this request execution.
415 /// </summary>
416 public IEngineContext Context
418 get { return engineContext; }
421 /// <summary>
422 /// Gets the Session dictionary.
423 /// </summary>
424 protected IDictionary Session
426 get { return engineContext.Session; }
429 /// <summary>
430 /// Gets a dictionary of volative items.
431 /// Ideal for showing success and failures messages.
432 /// </summary>
433 public Flash Flash
435 get { return engineContext.Flash; }
438 /// <summary>
439 /// Gets the web context of ASP.NET API.
440 /// </summary>
441 protected internal HttpContext HttpContext
443 get { return engineContext.UnderlyingContext; }
446 /// <summary>
447 /// Gets the request object.
448 /// </summary>
449 public IRequest Request
451 get { return Context.Request; }
454 /// <summary>
455 /// Gets the response object.
456 /// </summary>
457 public IResponse Response
459 get { return Context.Response; }
462 /// <summary>
463 /// Shortcut to <see cref="IRequest.Params"/>
464 /// </summary>
465 public NameValueCollection Params
467 get { return Request.Params; }
470 /// <summary>
471 /// Shortcut to <see cref="IRequest.Form"/>
472 /// </summary>
473 public NameValueCollection Form
475 get { return Request.Form; }
478 /// <summary>
479 /// Shortcut to <see cref="IRequest.QueryString"></see>
480 /// </summary>
481 public NameValueCollection Query
483 get { return Request.QueryString; }
486 /// <summary>
487 /// Gets the dynamic actions dictionary.
488 /// <para>
489 /// Can be used to insert dynamic actions on the controller instance.
490 /// </para>
491 /// </summary>
492 /// <value>The dynamic actions dictionary.</value>
493 public IDictionary<string, IDynamicAction> DynamicActions
495 get { return context.DynamicActions; }
498 /// <summary>
499 /// Shortcut to
500 /// <see cref="IResponse.IsClientConnected"/>
501 /// </summary>
502 protected bool IsClientConnected
504 get { return engineContext.Response.IsClientConnected; }
507 /// <summary>
508 /// Indicates that the current Action resulted from an ASP.NET PostBack.
509 /// As a result, this property is only relavent to controllers using
510 /// WebForms views. It is placed on the base Controller for convenience
511 /// only to avoid the need to extend the Controller or provide additional
512 /// helper classes. It is marked virtual to better support testing.
513 /// </summary>
514 protected virtual bool IsPostBack
518 NameValueCollection fields = Params;
519 return (fields["__VIEWSTATE"] != null) || (fields["__EVENTTARGET"] != null);
523 #endregion
525 #region Useful Operations
527 /// <summary>
528 /// Sets the engine context. Also initialize all required services by querying
529 /// <see cref="IEngineContext.Services"/>
530 /// </summary>
531 /// <param name="engineContext">The engine context.</param>
532 public virtual void SetEngineContext(IEngineContext engineContext)
534 this.engineContext = engineContext;
536 helperFactory = engineContext.Services.HelperFactory; // should not be null
537 serviceInitializer = engineContext.Services.ServiceInitializer; // should not be null
538 // urlBuilder = engineContext.Services.UrlBuilder; // should not be null (affects redirects)
539 filterFactory = engineContext.Services.FilterFactory; // should not be null
540 viewEngineManager = engineContext.Services.ViewEngineManager; // should not be null
541 actionSelector = engineContext.Services.ActionSelector; // should not be null
542 scaffoldSupport = engineContext.Services.ScaffoldSupport; // might be null
545 /// <summary>
546 /// Specifies the view to be processed after the action has finished its processing.
547 /// </summary>
548 /// <param name="name">view template name (the file extension is optional)</param>
549 public void RenderView(string name)
551 context.SelectedViewName = Path.Combine(ViewFolder, name);
554 /// <summary>
555 /// Specifies the view to be processed after the action has finished its processing.
556 /// </summary>
557 /// <param name="name">view template name (the file extension is optional)</param>
558 /// <param name="skipLayout">If set to <c>true</c>, no layout will be used when rendering the view</param>
559 public void RenderView(string name, bool skipLayout)
561 if (skipLayout) CancelLayout();
563 RenderView(name);
566 /// <summary>
567 /// Specifies the view to be processed after the action has finished its processing.
568 /// </summary>
569 /// <param name="name">view template name (the file extension is optional)</param>
570 /// <param name="skipLayout">If set to <c>true</c>, no layout will be used when rendering the view</param>
571 /// <param name="mimeType">The mime type to use on the reply</param>
572 public void RenderView(string name, bool skipLayout, string mimeType)
574 if (skipLayout) CancelLayout();
575 Response.ContentType = mimeType;
577 RenderView(name);
580 /// <summary>
581 /// Specifies the view to be processed after the action has finished its processing.
582 /// </summary>
583 /// <param name="controller">Controller name get view from (if you intend to user another controller's view</param>
584 /// <param name="name">view template name (the file extension is optional)</param>
585 public void RenderView(string controller, string name)
587 context.SelectedViewName = Path.Combine(controller, name);
590 /// <summary>
591 /// Specifies the view to be processed after the action has finished its processing.
592 /// </summary>
593 /// <param name="controller">Controller name get view from (if you intend to user another controller's view</param>
594 /// <param name="name">view template name (the file extension is optional)</param>
595 /// <param name="skipLayout">If set to <c>true</c>, no layout will be used when rendering the view</param>
596 public void RenderView(string controller, string name, bool skipLayout)
598 if (skipLayout) CancelLayout();
600 RenderView(controller, name);
603 /// <summary>
604 /// Specifies the view to be processed after the action has finished its processing.
605 /// </summary>
606 /// <param name="controller">Controller name get view from (if you intend to user another controller's view</param>
607 /// <param name="name">view template name (the file extension is optional)</param>
608 /// <param name="skipLayout">If set to <c>true</c>, no layout will be used when rendering the view</param>
609 /// <param name="mimeType">The mime type to use on the reply</param>
610 public void RenderView(string controller, string name, bool skipLayout, string mimeType)
612 if (skipLayout) CancelLayout();
614 Response.ContentType = mimeType;
615 RenderView(controller, name);
618 /// <summary>
619 /// Specifies the view to be processed after the action has finished its processing.
620 /// </summary>
621 /// <param name="controller">Controller name get view from (if you intend to user another controller's view</param>
622 /// <param name="name">view template name (the file extension is optional)</param>
623 /// <param name="mimeType">The mime type to use on the reply</param>
624 public void RenderView(string controller, string name, string mimeType)
626 Response.ContentType = mimeType;
627 RenderView(controller, name);
630 /// <summary>
631 /// Specifies the view to be processed and results are written to System.IO.TextWriter.
632 /// </summary>
633 /// <param name="output"></param>
634 /// <param name="name">The name of the view to process.</param>
635 public void InPlaceRenderView(TextWriter output, string name)
637 viewEngineManager.Process(Path.Combine(ViewFolder, name), output, Context, this, context);
640 /// <summary>
641 /// Specifies the shared view to be processed after the action has finished its
642 /// processing. (A partial view shared
643 /// by others views and usually in the root folder
644 /// of the view directory).
645 /// </summary>
646 public void RenderSharedView(string name)
648 context.SelectedViewName = name;
651 /// <summary>
652 /// Specifies the shared view to be processed after the action has finished its
653 /// processing. (A partial view shared
654 /// by others views and usually in the root folder
655 /// of the view directory).
656 /// </summary>
657 public void RenderSharedView(string name, bool skipLayout)
659 if (skipLayout) CancelLayout();
661 RenderSharedView(name);
664 /// <summary>
665 /// Specifies the shared view to be processed and results are written to System.IO.TextWriter.
666 /// (A partial view shared by others views and usually in the root folder
667 /// of the view directory).
668 /// </summary>
669 /// <param name="output"></param>
670 /// <param name="name">The name of the view to process.</param>
671 public void InPlaceRenderSharedView(TextWriter output, string name)
673 viewEngineManager.Process(name, output, Context, this, context);
676 /// <summary>
677 /// Cancels the view processing.
678 /// </summary>
679 public void CancelView()
681 context.SelectedViewName = null;
684 /// <summary>
685 /// Cancels the layout processing.
686 /// </summary>
687 public void CancelLayout()
689 LayoutName = null;
692 /// <summary>
693 /// Cancels the view processing and writes
694 /// the specified contents to the browser
695 /// </summary>
696 public void RenderText(string contents)
698 CancelView();
700 engineContext.Response.Write(contents);
703 /// <summary>
704 /// Cancels the view processing and writes
705 /// the specified contents to the browser
706 /// </summary>
707 public void RenderText(string contents, params object[] args)
709 RenderText(String.Format(contents, args));
712 /// <summary>
713 /// Cancels the view processing and writes
714 /// the specified contents to the browser
715 /// </summary>
716 public void RenderText(IFormatProvider formatProvider, string contents, params object[] args)
718 RenderText(String.Format(formatProvider, contents, args));
721 /// <summary>
722 /// Sends raw contents to be rendered directly by the view engine.
723 /// It's up to the view engine just to apply the layout and nothing else.
724 /// </summary>
725 /// <param name="contents">Contents to be rendered.</param>
726 public void DirectRender(string contents)
728 CancelView();
730 if (directRenderInvoked)
732 throw new ControllerException("DirectRender should be called only once.");
735 directRenderInvoked = true;
737 viewEngineManager.RenderStaticWithinLayout(contents, engineContext, this, context);
740 /// <summary>
741 /// Returns true if the specified template exists.
742 /// </summary>
743 /// <param name="templateName"></param>
744 public bool HasTemplate(string templateName)
746 return viewEngineManager.HasTemplate(templateName);
749 #region Redirects
751 /// <summary>
752 /// Redirects to another action in the same controller.
753 /// </summary>
754 /// <param name="action">The action name</param>
755 public void RedirectToAction(string action)
757 RedirectToAction(action, (NameValueCollection) null);
760 /// <summary>
761 /// Redirects to another action in the same controller passing the specified querystring parameters.
762 /// </summary>
763 /// <param name="action">The action name</param>
764 /// <param name="queryStringParameters">The querystring entries</param>
765 public void RedirectToAction(string action, IDictionary queryStringParameters)
767 if (queryStringParameters != null)
769 Response.Redirect(AreaName, Name, TransformActionName(action), queryStringParameters);
771 else
773 Response.Redirect(AreaName, Name, TransformActionName(action));
777 /// <summary>
778 /// Redirects to another action in the same controller passing the specified querystring parameters.
779 /// </summary>
780 /// <param name="action">The action name</param>
781 /// <param name="queryStringParameters">The querystring entries</param>
782 public void RedirectToAction(string action, NameValueCollection queryStringParameters)
784 if (queryStringParameters != null)
786 Response.Redirect(AreaName, Name, TransformActionName(action), queryStringParameters);
788 else
790 Response.Redirect(AreaName, Name, TransformActionName(action));
794 /// <summary>
795 /// Redirects to another action in the same controller passing the specified querystring parameters.
796 /// </summary>
797 /// <param name="action">The action name</param>
798 /// <param name="queryStringParameters">The querystring entries</param>
799 public void RedirectToAction(string action, params string[] queryStringParameters)
801 RedirectToAction(action, DictHelper.Create(queryStringParameters));
804 /// <summary>
805 /// Redirects to another action in the same controller passing the specified querystring parameters.
806 /// </summary>
807 /// <param name="action">The action name</param>
808 /// <param name="queryStringAnonymousDictionary">The querystring entries as an anonymous dictionary</param>
809 public void RedirectToAction(string action, object queryStringAnonymousDictionary)
811 RedirectToAction(action, new ReflectionBasedDictionaryAdapter(queryStringAnonymousDictionary));
814 /// <summary>
815 /// Redirects to the site root directory (<c>Context.ApplicationPath + "/"</c>).
816 /// </summary>
817 public void RedirectToSiteRoot()
819 Response.RedirectToSiteRoot();
822 /// <summary>
823 /// Redirects to the specified url
824 /// </summary>
825 /// <param name="url">An relative or absolute URL to redirect the client to</param>
826 public void RedirectToUrl(string url)
828 Response.RedirectToUrl(url);
831 /// <summary>
832 /// Redirects to the specified url
833 /// </summary>
834 /// <param name="url">An relative or absolute URL to redirect the client to</param>
835 /// <param name="endProcess">if set to <c>true</c>, sends the redirect and
836 /// kills the current request process.</param>
837 public void RedirectToUrl(string url, bool endProcess)
839 Response.RedirectToUrl(url, endProcess);
842 /// <summary>
843 /// Redirects to the specified url
844 /// </summary>
845 /// <param name="url">An relative or absolute URL to redirect the client to</param>
846 /// <param name="queryStringParameters">The querystring entries</param>
847 public void RedirectToUrl(string url, IDictionary queryStringParameters)
849 Response.RedirectToUrl(url, queryStringParameters);
852 /// <summary>
853 /// Redirects to the specified url
854 /// </summary>
855 /// <param name="url">An relative or absolute URL to redirect the client to</param>
856 /// <param name="queryStringParameters">The querystring entries</param>
857 public void RedirectToUrl(string url, NameValueCollection queryStringParameters)
859 Response.RedirectToUrl(url, queryStringParameters);
862 /// <summary>
863 /// Redirects to the specified url
864 /// </summary>
865 /// <param name="url">An relative or absolute URL to redirect the client to</param>
866 /// <param name="queryStringParameters">The querystring entries</param>
867 public void RedirectToUrl(string url, params string[] queryStringParameters)
869 Response.RedirectToUrl(url, queryStringParameters);
872 /// <summary>
873 /// Redirects to the specified url
874 /// </summary>
875 /// <param name="url">An relative or absolute URL to redirect the client to</param>
876 /// <param name="queryStringAnonymousDictionary">The querystring entries as an anonymous dictionary</param>
877 public void RedirectToUrl(string url, object queryStringAnonymousDictionary)
879 Response.RedirectToUrl(url, queryStringAnonymousDictionary);
882 /// <summary>
883 /// Redirects to another controller's action.
884 /// </summary>
885 /// <param name="controller">The controller name to be redirected to.</param>
886 /// <param name="action">The desired action on the target controller.</param>
887 public void Redirect(string controller, string action)
889 Response.Redirect(controller, action);
892 /// <summary>
893 /// Redirects to another controller's action with the specified parameters.
894 /// </summary>
895 /// <param name="controller">The controller name to be redirected to.</param>
896 /// <param name="action">The desired action on the target controller.</param>
897 /// <param name="queryStringParameters">The querystring entries</param>
898 public void Redirect(string controller, string action, NameValueCollection queryStringParameters)
900 Response.Redirect(controller, action, queryStringParameters);
903 /// <summary>
904 /// Redirects to another controller's action with the specified parameters.
905 /// </summary>
906 /// <param name="controller">The controller name to be redirected to.</param>
907 /// <param name="action">The desired action on the target controller.</param>
908 /// <param name="queryStringParameters">The querystring entries</param>
909 public void Redirect(string controller, string action, IDictionary queryStringParameters)
911 Response.Redirect(controller, action, queryStringParameters);
914 /// <summary>
915 /// Redirects to another controller's action with the specified parameters.
916 /// </summary>
917 /// <param name="controller">The controller name to be redirected to.</param>
918 /// <param name="action">The desired action on the target controller.</param>
919 /// <param name="queryStringAnonymousDictionary">The querystring entries as an anonymous dictionary</param>
920 public void Redirect(string controller, string action, object queryStringAnonymousDictionary)
922 Response.Redirect(controller, action, queryStringAnonymousDictionary);
925 /// <summary>
926 /// Redirects to another controller's action in a different area.
927 /// </summary>
928 /// <param name="area">The area the target controller belongs to.</param>
929 /// <param name="controller">The controller name to be redirected to.</param>
930 /// <param name="action">The desired action on the target controller.</param>
931 public void Redirect(string area, string controller, string action)
933 Response.Redirect(area, controller, action);
936 /// <summary>
937 /// Redirects to another controller's action in a different area with the specified parameters.
938 /// </summary>
939 /// <param name="area">The area the target controller belongs to.</param>
940 /// <param name="controller">The controller name to be redirected to.</param>
941 /// <param name="action">The desired action on the target controller.</param>
942 /// <param name="queryStringParameters">The querystring entries</param>
943 public void Redirect(string area, string controller, string action, IDictionary queryStringParameters)
945 Response.Redirect(area, controller, action, queryStringParameters);
948 /// <summary>
949 /// Redirects to another controller's action in a different area with the specified parameters.
950 /// </summary>
951 /// <param name="area">The area the target controller belongs to.</param>
952 /// <param name="controller">The controller name to be redirected to.</param>
953 /// <param name="action">The desired action on the target controller.</param>
954 /// <param name="queryStringParameters">The querystring entries</param>
955 public void Redirect(string area, string controller, string action, NameValueCollection queryStringParameters)
957 Response.Redirect(area, controller, action, queryStringParameters);
960 /// <summary>
961 /// Redirects to another controller's action in a different area with the specified parameters.
962 /// </summary>
963 /// <param name="area">The area the target controller belongs to.</param>
964 /// <param name="controller">The controller name to be redirected to.</param>
965 /// <param name="action">The desired action on the target controller.</param>
966 /// <param name="queryStringAnonymousDictionary">The querystring entries as an anonymous dictionary</param>
967 public void Redirect(string area, string controller, string action, object queryStringAnonymousDictionary)
969 Response.Redirect(area, controller, action, queryStringAnonymousDictionary);
972 /// <summary>
973 /// Tries to resolve the target redirect url by using the routing rules registered.
974 /// </summary>
975 /// <param name="action">The desired action on the target controller.</param>
976 /// <param name="useCurrentRouteParams">if set to <c>true</c> the current request matching route rules will be used.</param>
977 public void RedirectUsingRoute(string action, bool useCurrentRouteParams)
979 Response.RedirectUsingRoute(Name, action, useCurrentRouteParams);
982 /// <summary>
983 /// Tries to resolve the target redirect url by using the routing rules registered.
984 /// </summary>
985 /// <param name="action">The desired action on the target controller.</param>
986 /// <param name="routeParameters">The routing rule parameters.</param>
987 public void RedirectUsingRoute(string action, IDictionary routeParameters)
989 Response.RedirectUsingRoute(Name, action, routeParameters);
992 /// <summary>
993 /// Tries to resolve the target redirect url by using the routing rules registered.
994 /// </summary>
995 /// <param name="action">The desired action on the target controller.</param>
996 /// <param name="routeParameters">The routing rule parameters.</param>
997 public void RedirectUsingRoute(string action, object routeParameters)
999 Response.RedirectUsingRoute(Name, action, routeParameters);
1002 /// <summary>
1003 /// Tries to resolve the target redirect url by using the routing rules registered.
1004 /// </summary>
1005 /// <param name="controller">The controller name to be redirected to.</param>
1006 /// <param name="action">The desired action on the target controller.</param>
1007 /// <param name="useCurrentRouteParams">if set to <c>true</c> the current request matching route rules will be used.</param>
1008 public void RedirectUsingRoute(string controller, string action, bool useCurrentRouteParams)
1010 Response.RedirectUsingRoute(controller, action, useCurrentRouteParams);
1013 /// <summary>
1014 /// Tries to resolve the target redirect url by using the routing rules registered.
1015 /// </summary>
1016 /// <param name="area">The area the target controller belongs to.</param>
1017 /// <param name="controller">The controller name to be redirected to.</param>
1018 /// <param name="action">The desired action on the target controller.</param>
1019 /// <param name="useCurrentRouteParams">if set to <c>true</c> the current request matching route rules will be used.</param>
1020 public void RedirectUsingRoute(string area, string controller, string action, bool useCurrentRouteParams)
1022 Response.RedirectUsingRoute(area, controller, action, useCurrentRouteParams);
1025 /// <summary>
1026 /// Tries to resolve the target redirect url by using the routing rules registered.
1027 /// </summary>
1028 /// <param name="controller">The controller name to be redirected to.</param>
1029 /// <param name="action">The desired action on the target controller.</param>
1030 /// <param name="routeParameters">The routing rule parameters.</param>
1031 public void RedirectUsingRoute(string controller, string action, IDictionary routeParameters)
1033 Response.RedirectUsingRoute(controller, action, routeParameters);
1036 /// <summary>
1037 /// Tries to resolve the target redirect url by using the routing rules registered.
1038 /// </summary>
1039 /// <param name="controller">The controller name to be redirected to.</param>
1040 /// <param name="action">The desired action on the target controller.</param>
1041 /// <param name="routeParameters">The routing rule parameters.</param>
1042 public void RedirectUsingRoute(string controller, string action, object routeParameters)
1044 Response.RedirectUsingRoute(controller, action, routeParameters);
1047 /// <summary>
1048 /// Tries to resolve the target redirect url by using the routing rules registered.
1049 /// </summary>
1050 /// <param name="area">The area the target controller belongs to.</param>
1051 /// <param name="controller">The controller name to be redirected to.</param>
1052 /// <param name="action">The desired action on the target controller.</param>
1053 /// <param name="routeParameters">The routing rule parameters.</param>
1054 public void RedirectUsingRoute(string area, string controller, string action, IDictionary routeParameters)
1056 Response.RedirectUsingRoute(area, controller, action, routeParameters);
1059 /// <summary>
1060 /// Tries to resolve the target redirect url by using the routing rules registered.
1061 /// </summary>
1062 /// <param name="area">The area the target controller belongs to.</param>
1063 /// <param name="controller">The controller name to be redirected to.</param>
1064 /// <param name="action">The desired action on the target controller.</param>
1065 /// <param name="routeParameters">The routing rule parameters.</param>
1066 public void RedirectUsingRoute(string area, string controller, string action, object routeParameters)
1068 Response.RedirectUsingRoute(area, controller, action, routeParameters);
1071 #endregion
1073 #region Redirect utilities
1075 /// <summary>
1076 /// Creates a querystring string representation of the namevalue collection.
1077 /// </summary>
1078 /// <param name="parameters">The parameters.</param>
1079 /// <returns></returns>
1080 protected string ToQueryString(NameValueCollection parameters)
1082 return CommonUtils.BuildQueryString(Context.Server, parameters, false);
1085 /// <summary>
1086 /// Creates a querystring string representation of the entries in the dictionary.
1087 /// </summary>
1088 /// <param name="parameters">The parameters.</param>
1089 /// <returns></returns>
1090 protected string ToQueryString(IDictionary parameters)
1092 return CommonUtils.BuildQueryString(Context.Server, parameters, false);
1095 #endregion
1097 #endregion
1099 #region Email operations
1101 /// <summary>
1102 /// Creates an instance of <see cref="Message"/>
1103 /// using the specified template for the body
1104 /// </summary>
1105 /// <param name="templateName">
1106 /// Name of the template to load.
1107 /// Will look in Views/mail for that template file.
1108 /// </param>
1109 /// <returns>An instance of <see cref="Message"/></returns>
1110 [Obsolete]
1111 public Message RenderMailMessage(string templateName)
1113 return RenderMailMessage(templateName, false);
1116 /// <summary>
1117 /// Creates an instance of <see cref="Message"/>
1118 /// using the specified template for the body
1119 /// </summary>
1120 /// <param name="templateName">
1121 /// Name of the template to load.
1122 /// Will look in Views/mail for that template file.
1123 /// </param>
1124 /// <param name="doNotApplyLayout">If <c>true</c>, it will skip the layout</param>
1125 /// <returns>An instance of <see cref="Message"/></returns>
1126 [Obsolete]
1127 public Message RenderMailMessage(string templateName, bool doNotApplyLayout)
1129 IEmailTemplateService templateService = engineContext.Services.EmailTemplateService;
1130 return templateService.RenderMailMessage(templateName, Context, this, ControllerContext, doNotApplyLayout);
1133 /// <summary>
1134 /// Creates an instance of <see cref="Message"/>
1135 /// using the specified template for the body
1136 /// </summary>
1137 /// <param name="templateName">Name of the template to load.
1138 /// Will look in Views/mail for that template file.</param>
1139 /// <param name="layoutName">Name of the layout.</param>
1140 /// <param name="parameters">The parameters.</param>
1141 /// <returns>An instance of <see cref="Message"/></returns>
1142 public Message RenderMailMessage(string templateName, string layoutName, IDictionary parameters)
1144 IEmailTemplateService templateService = engineContext.Services.EmailTemplateService;
1145 return templateService.RenderMailMessage(templateName, layoutName, parameters);
1148 /// <summary>
1149 /// Creates an instance of <see cref="Message"/>
1150 /// using the specified template for the body
1151 /// </summary>
1152 /// <param name="templateName">Name of the template to load.
1153 /// Will look in Views/mail for that template file.</param>
1154 /// <param name="layoutName">Name of the layout.</param>
1155 /// <param name="parameters">The parameters.</param>
1156 /// <returns>An instance of <see cref="Message"/></returns>
1157 public Message RenderMailMessage(string templateName, string layoutName, IDictionary<string, object> parameters)
1159 IEmailTemplateService templateService = engineContext.Services.EmailTemplateService;
1160 return templateService.RenderMailMessage(templateName, layoutName, parameters);
1163 /// <summary>
1164 /// Creates an instance of <see cref="Message"/>
1165 /// using the specified template for the body
1166 /// </summary>
1167 /// <param name="templateName">Name of the template to load.
1168 /// Will look in Views/mail for that template file.</param>
1169 /// <param name="layoutName">Name of the layout.</param>
1170 /// <param name="parameters">The parameters.</param>
1171 /// <returns>An instance of <see cref="Message"/></returns>
1172 public Message RenderMailMessage(string templateName, string layoutName, object parameters)
1174 IEmailTemplateService templateService = engineContext.Services.EmailTemplateService;
1175 return templateService.RenderMailMessage(templateName, layoutName, parameters);
1178 /// <summary>
1179 /// Attempts to deliver the Message using the server specified on the web.config.
1180 /// </summary>
1181 /// <param name="message">The instance of System.Web.Mail.MailMessage that will be sent</param>
1182 public void DeliverEmail(Message message)
1186 IEmailSender sender = engineContext.Services.EmailSender;
1187 sender.Send(message);
1189 catch(Exception ex)
1191 if (logger.IsErrorEnabled)
1193 logger.Error("Error sending e-mail", ex);
1196 throw new MonoRailException("Error sending e-mail", ex);
1200 /// <summary>
1201 /// Renders and delivers the e-mail message.
1202 /// <seealso cref="DeliverEmail"/>
1203 /// </summary>
1204 /// <param name="templateName"></param>
1205 [Obsolete]
1206 public void RenderEmailAndSend(string templateName)
1208 Message message = RenderMailMessage(templateName);
1209 DeliverEmail(message);
1212 #endregion
1214 #region Lifecycle (overridables)
1216 /// <summary>
1217 /// Initializes this instance. Implementors
1218 /// can use this method to perform initialization
1219 /// </summary>
1220 protected virtual void Initialize()
1224 #endregion
1226 #region Resources/i18n
1228 /// <summary>
1229 /// Creates the controller level resources.
1230 /// </summary>
1231 protected virtual void CreateControllerLevelResources()
1233 CreateResources(MetaDescriptor.Resources);
1236 /// <summary>
1237 /// Creates the controller level resources.
1238 /// </summary>
1239 /// <param name="action">The action.</param>
1240 protected virtual void CreateActionLevelResources(IExecutableAction action)
1242 CreateResources(action.Resources);
1245 /// <summary>
1246 /// Creates the resources and adds them to the <see cref="IControllerContext.Resources"/>.
1247 /// </summary>
1248 /// <param name="resources">The resources.</param>
1249 protected virtual void CreateResources(ResourceDescriptor[] resources)
1251 if (resources == null || resources.Length == 0)
1253 return;
1256 Assembly typeAssembly = GetType().Assembly;
1258 IResourceFactory resourceFactory = engineContext.Services.ResourceFactory;
1260 foreach(ResourceDescriptor resDesc in resources)
1262 if (ControllerContext.Resources.ContainsKey(resDesc.Name))
1264 throw new MonoRailException("There is a duplicated entry on the resource dictionary. Resource entry name: " +
1265 resDesc.Name);
1268 ControllerContext.Resources.Add(resDesc.Name, resourceFactory.Create(resDesc, typeAssembly));
1272 #endregion
1274 /// <summary>
1275 /// Gives a change to subclass
1276 /// to override the layout resolution code
1277 /// </summary>
1278 protected virtual void ResolveLayout()
1280 context.LayoutNames = ObtainDefaultLayoutName();
1283 private void RunActionAndRenderView()
1285 IExecutableAction action = null;
1286 Exception actionException = null;
1287 bool cancel;
1291 action = SelectAction(Action);
1293 if (action == null)
1295 throw new MonoRailException(404, "Not Found", "Could not find action named " +
1296 Action + " on controller " + AreaName + "\\" + Name);
1299 EnsureActionIsAccessibleWithCurrentHttpVerb(action);
1301 RunBeforeActionFilters(action, out cancel);
1303 CreateControllerLevelResources();
1304 CreateActionLevelResources(action);
1306 if (cancel) return;
1308 if (BeforeAction != null)
1310 BeforeAction(action, engineContext, this, context);
1313 action.Execute(engineContext, this, context);
1315 // Action executed successfully, so it's safe to process the cache configurer
1316 if ((MetaDescriptor.CacheConfigurer != null || action.CachePolicyConfigurer != null) &&
1317 !Response.WasRedirected && Response.StatusCode == 200)
1319 ConfigureCachePolicy(action);
1322 catch(MonoRailException ex)
1324 if (Response.StatusCode == 200 && ex.HttpStatusCode.HasValue)
1326 Response.StatusCode = ex.HttpStatusCode.Value;
1327 Response.StatusDescription = ex.HttpStatusDesc;
1330 actionException = ex;
1332 RegisterExceptionAndNotifyExtensions(actionException);
1334 catch(Exception ex)
1336 if (Response.StatusCode == 200)
1338 Response.StatusCode = 500;
1339 Response.StatusDescription = "Error processing action";
1342 actionException = (ex is TargetInvocationException) ? ex.InnerException : ex;
1344 RegisterExceptionAndNotifyExtensions(actionException);
1346 finally
1348 // AfterAction event: always executed
1349 if (AfterAction != null)
1351 AfterAction(action, engineContext, this, context);
1355 RunAfterActionFilters(action, out cancel);
1356 if (cancel) return;
1358 if (engineContext.Response.WasRedirected) // No need to process view or rescue in this case
1360 return;
1363 if (actionException == null)
1365 if (context.SelectedViewName != null)
1367 ProcessView();
1368 RunAfterRenderingFilters(action);
1371 else
1373 if (!ProcessRescue(action, actionException))
1375 throw actionException;
1380 /// <summary>
1381 /// Configures the cache policy.
1382 /// </summary>
1383 /// <param name="action">The action.</param>
1384 protected virtual void ConfigureCachePolicy(IExecutableAction action)
1386 ICachePolicyConfigurer configurer = action.CachePolicyConfigurer ?? MetaDescriptor.CacheConfigurer;
1388 configurer.Configure(Response.CachePolicy);
1391 /// <summary>
1392 /// Selects the appropriate action.
1393 /// </summary>
1394 /// <param name="action">The action name.</param>
1395 /// <returns></returns>
1396 protected virtual IExecutableAction SelectAction(string action)
1398 // For backward compatibility purposes
1399 MethodInfo method = SelectMethod(action, MetaDescriptor.Actions, engineContext.Request, context.CustomActionParameters);
1401 if (method != null)
1403 ActionMetaDescriptor actionMeta = MetaDescriptor.GetAction(method);
1405 return new ActionMethodExecutorCompatible(method, actionMeta ?? new ActionMetaDescriptor(), InvokeMethod);
1408 // New supported way
1409 return actionSelector.Select(engineContext, this, context);
1412 /// <summary>
1413 /// Invokes the scaffold support if the controller
1414 /// is associated with a scaffold
1415 /// </summary>
1416 protected virtual void ProcessScaffoldIfAvailable()
1418 if (MetaDescriptor.Scaffoldings.Count != 0)
1420 if (scaffoldSupport == null)
1422 String message = "You must enable scaffolding support on the " +
1423 "configuration file, or, to use the standard ActiveRecord support " +
1424 "copy the necessary assemblies to the bin folder.";
1426 throw new MonoRailException(message);
1429 scaffoldSupport.Process(engineContext, this, context);
1433 /// <summary>
1434 /// Ensures the action is accessible with current HTTP verb.
1435 /// </summary>
1436 /// <param name="action">The action.</param>
1437 protected virtual void EnsureActionIsAccessibleWithCurrentHttpVerb(IExecutableAction action)
1439 Verb allowedVerbs = action.AccessibleThroughVerb;
1441 if (allowedVerbs == Verb.Undefined)
1443 return;
1446 string method = engineContext.Request.HttpMethod;
1448 Verb currentVerb = (Verb) Enum.Parse(typeof(Verb), method, true);
1450 if ((allowedVerbs & currentVerb) != currentVerb)
1452 throw new MonoRailException(403, "Forbidden",
1453 string.Format("Access to the action [{0}] " +
1454 "on controller [{1}] is not allowed to the http verb [{2}].",
1455 Action, Name, method));
1459 #region Views and Layout
1461 /// <summary>
1462 /// Obtains the name of the default layout.
1463 /// </summary>
1464 /// <returns></returns>
1465 protected virtual String[] ObtainDefaultLayoutName()
1467 if (MetaDescriptor.Layout != null)
1469 return MetaDescriptor.Layout.LayoutNames;
1471 else
1473 String defaultLayout = String.Format("layouts/{0}", Name);
1475 if (viewEngineManager.HasTemplate(defaultLayout))
1477 return new String[] {Name};
1481 return null;
1484 /// <summary>
1485 /// Processes the view.
1486 /// </summary>
1487 protected virtual void ProcessView()
1489 if (context.SelectedViewName != null)
1491 viewEngineManager.Process(context.SelectedViewName, engineContext.Response.Output, engineContext, this, context);
1495 #endregion
1497 #region Helpers
1499 /// <summary>
1500 /// Creates the and initialize helpers associated with a controller.
1501 /// </summary>
1502 public virtual void CreateAndInitializeHelpers()
1504 IDictionary helpers = context.Helpers;
1506 // Custom helpers
1508 foreach(HelperDescriptor helper in MetaDescriptor.Helpers)
1510 bool initialized;
1511 object helperInstance = helperFactory.Create(helper.HelperType, engineContext, out initialized);
1513 if (!initialized)
1515 serviceInitializer.Initialize(helperInstance, engineContext);
1518 if (helpers.Contains(helper.Name))
1520 throw new ControllerException(String.Format("Found a duplicate helper " +
1521 "attribute named '{0}' on controller '{1}'", helper.Name, Name));
1524 helpers.Add(helper.Name, helperInstance);
1527 CreateStandardHelpers();
1530 /// <summary>
1531 /// Creates the standard helpers.
1532 /// </summary>
1533 public virtual void CreateStandardHelpers()
1535 AbstractHelper[] builtInHelpers =
1536 new AbstractHelper[]
1538 new AjaxHelper(engineContext), new BehaviourHelper(engineContext),
1539 new UrlHelper(engineContext), new TextHelper(engineContext),
1540 new EffectsFatHelper(engineContext), new ScriptaculousHelper(engineContext),
1541 new DateFormatHelper(engineContext), new HtmlHelper(engineContext),
1542 new ValidationHelper(engineContext), new DictHelper(engineContext),
1543 new PaginationHelper(engineContext), new FormHelper(engineContext),
1544 new JSONHelper(engineContext), new ZebdaHelper(engineContext)
1547 foreach(AbstractHelper helper in builtInHelpers)
1549 context.Helpers.Add(helper);
1551 Type helperType = helper.GetType();
1553 if (helperType == typeof(FormHelper) || helperType == typeof(AjaxHelper))
1555 serviceInitializer.Initialize(helper, engineContext);
1560 #endregion
1562 #region Filters
1564 private void CreateFiltersDescriptors()
1566 if (MetaDescriptor.Filters.Length != 0)
1568 filters = CopyFilterDescriptors();
1572 private void RunBeforeActionFilters(IExecutableAction action, out bool cancel)
1574 cancel = false;
1575 if (action.ShouldSkipAllFilters) return;
1577 if (!ProcessFilters(action, ExecuteWhen.BeforeAction))
1579 cancel = true;
1580 return; // A filter returned false... so we stop
1584 private void RunAfterActionFilters(IExecutableAction action, out bool cancel)
1586 cancel = false;
1587 if (action == null) return;
1589 if (action.ShouldSkipAllFilters) return;
1591 if (!ProcessFilters(action, ExecuteWhen.AfterAction))
1593 cancel = true;
1594 return; // A filter returned false... so we stop
1598 private void RunAfterRenderingFilters(IExecutableAction action)
1600 if (action.ShouldSkipAllFilters) return;
1602 ProcessFilters(action, ExecuteWhen.AfterRendering);
1605 /// <summary>
1606 /// Identifies if no filter should run for the given action.
1607 /// </summary>
1608 /// <param name="action">The action.</param>
1609 /// <returns></returns>
1610 protected virtual bool ShouldSkipFilters(IExecutableAction action)
1612 if (filters == null)
1614 // No filters, so skip
1615 return true;
1618 return action.ShouldSkipAllFilters;
1620 // ActionMetaDescriptor actionMeta = MetaDescriptor.GetAction(method);
1622 // if (actionMeta.SkipFilters.Count == 0)
1623 // {
1624 // // Nothing against filters declared for this action
1625 // return false;
1626 // }
1628 // foreach(SkipFilterAttribute skipfilter in actionMeta.SkipFilters)
1629 // {
1630 // // SkipAllFilters handling...
1631 // if (skipfilter.BlanketSkip)
1632 // {
1633 // return true;
1634 // }
1636 // filtersToSkip[skipfilter.FilterType] = String.Empty;
1637 // }
1639 // return false;
1642 /// <summary>
1643 /// Clones all Filter descriptors, in order to get a writable copy.
1644 /// </summary>
1645 protected internal FilterDescriptor[] CopyFilterDescriptors()
1647 FilterDescriptor[] clone = (FilterDescriptor[]) MetaDescriptor.Filters.Clone();
1649 for(int i = 0; i < clone.Length; i++)
1651 clone[i] = (FilterDescriptor) clone[i].Clone();
1654 return clone;
1657 private bool ProcessFilters(IExecutableAction action, ExecuteWhen when)
1659 foreach(FilterDescriptor desc in filters)
1661 if (action.ShouldSkipFilter(desc.FilterType))
1663 continue;
1666 if ((desc.When & when) != 0)
1668 if (!ProcessFilter(when, desc))
1670 return false;
1675 return true;
1678 private bool ProcessFilter(ExecuteWhen when, FilterDescriptor desc)
1680 if (desc.FilterInstance == null)
1682 desc.FilterInstance = filterFactory.Create(desc.FilterType);
1684 IFilterAttributeAware filterAttAware = desc.FilterInstance as IFilterAttributeAware;
1686 if (filterAttAware != null)
1688 filterAttAware.Filter = desc.Attribute;
1694 if (logger.IsDebugEnabled)
1696 logger.DebugFormat("Running filter {0}/{1}", when, desc.FilterType.FullName);
1699 return desc.FilterInstance.Perform(when, engineContext, this, context);
1701 catch(Exception ex)
1703 if (logger.IsErrorEnabled)
1705 logger.ErrorFormat("Error processing filter " + desc.FilterType.FullName, ex);
1708 throw;
1712 private void DisposeFilters()
1714 if (filters == null) return;
1716 foreach(FilterDescriptor desc in filters)
1718 if (desc.FilterInstance != null)
1720 filterFactory.Release(desc.FilterInstance);
1725 #endregion
1727 #region Rescue
1729 /// <summary>
1730 /// Performs the rescue.
1731 /// </summary>
1732 /// <param name="action">The action (can be null in the case of dynamic actions).</param>
1733 /// <param name="actionException">The exception.</param>
1734 /// <returns></returns>
1735 protected bool ProcessRescue(IExecutableAction action, Exception actionException)
1737 if (action != null && action.ShouldSkipRescues)
1739 return false;
1742 Type exceptionType = actionException.GetType();
1744 RescueDescriptor desc = action != null ? action.GetRescueFor(exceptionType) : null;
1746 if (desc == null)
1748 desc = GetControllerRescueFor(exceptionType);
1751 if (desc != null)
1755 if (desc.RescueController != null)
1757 CreateAndProcessRescueController(desc, actionException);
1759 else
1761 context.SelectedViewName = Path.Combine("rescues", desc.ViewName);
1763 ProcessView();
1766 return true;
1768 catch(Exception exception)
1770 // In this situation, the rescue view could not be found
1771 // So we're back to the default error exibition
1773 if (logger.IsFatalEnabled)
1775 logger.FatalFormat("Failed to process rescue view. View name " +
1776 context.SelectedViewName, exception);
1781 return false;
1784 /// <summary>
1785 /// Gets the best rescue that matches the exception type
1786 /// </summary>
1787 /// <param name="exceptionType">Type of the exception.</param>
1788 /// <returns></returns>
1789 protected virtual RescueDescriptor GetControllerRescueFor(Type exceptionType)
1791 return RescueUtils.SelectBest(MetaDescriptor.Rescues, exceptionType);
1794 private void CreateAndProcessRescueController(RescueDescriptor desc, Exception actionException)
1796 IController rescueController = engineContext.Services.ControllerFactory.CreateController(desc.RescueController);
1798 ControllerMetaDescriptor rescueControllerMeta =
1799 engineContext.Services.ControllerDescriptorProvider.BuildDescriptor(rescueController);
1801 ControllerDescriptor rescueControllerDesc = rescueControllerMeta.ControllerDescriptor;
1803 IControllerContext rescueControllerContext = engineContext.Services.ControllerContextFactory.Create(
1804 rescueControllerDesc.Area, rescueControllerDesc.Name, desc.RescueMethod.Name,
1805 rescueControllerMeta);
1807 rescueControllerContext.CustomActionParameters["exception"] = actionException;
1808 rescueControllerContext.CustomActionParameters["controller"] = this;
1809 rescueControllerContext.CustomActionParameters["controllerContext"] = ControllerContext;
1811 rescueController.Process(engineContext, rescueControllerContext);
1814 #endregion
1816 /// <summary>
1817 /// Pendent
1818 /// </summary>
1819 /// <param name="action">The action.</param>
1820 /// <param name="actions">The actions.</param>
1821 /// <param name="request">The request.</param>
1822 /// <param name="actionArgs">The action args.</param>
1823 /// <returns></returns>
1824 protected virtual MethodInfo SelectMethod(string action, IDictionary actions, IRequest request,
1825 IDictionary<string, object> actionArgs)
1827 return null;
1830 /// <summary>
1831 /// Pendent
1832 /// </summary>
1833 /// <param name="method">The method.</param>
1834 /// <param name="request">The request.</param>
1835 /// <param name="methodArgs">The method args.</param>
1836 protected virtual object InvokeMethod(MethodInfo method, IRequest request,
1837 IDictionary<string, object> methodArgs)
1839 return method.Invoke(this, new object[0]);
1842 /// <summary>
1843 /// Creates the default validator runner.
1844 /// </summary>
1845 /// <param name="validatorRegistry">The validator registry.</param>
1846 /// <returns></returns>
1847 /// <remarks>
1848 /// You can override this method to create a runner
1849 /// with some different configuration
1850 /// </remarks>
1851 protected virtual ValidatorRunner CreateValidatorRunner(IValidatorRegistry validatorRegistry)
1853 if (validatorRegistry == null)
1855 throw new ArgumentNullException("validatorRegistry");
1858 return new ValidatorRunner(validatorRegistry);
1861 /// <summary>
1862 /// Gives a chance to subclasses to format the action name properly
1863 /// </summary>
1864 /// <param name="action">Raw action name</param>
1865 /// <returns>Properly formatted action name</returns>
1866 protected virtual string TransformActionName(string action)
1868 return action;
1871 /// <summary>
1872 /// Registers the exception and notify extensions.
1873 /// </summary>
1874 /// <param name="exception">The exception.</param>
1875 protected void RegisterExceptionAndNotifyExtensions(Exception exception)
1877 engineContext.LastException = exception;
1878 engineContext.Services.ExtensionManager.RaiseActionError(engineContext);