Removed untyped contructor from ComponentRegistration and add a protected setter.
[castle.git] / MonoRail / Castle.MonoRail.Framework / Controller.cs
blobda73c1193c05d30878ba4db166381e2cd767c78a
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;
44 private bool isContextualized;
46 private IHelperFactory helperFactory;
47 private IServiceInitializer serviceInitializer;
48 private IFilterFactory filterFactory;
49 private IViewEngineManager viewEngineManager;
50 private IActionSelector actionSelector;
51 private IScaffoldingSupport scaffoldSupport;
52 private FilterDescriptor[] filters = new FilterDescriptor[0];
53 private ValidatorRunner validatorRunner;
54 private Dictionary<object, ErrorSummary> validationSummaryPerInstance;
55 private Dictionary<object, ErrorList> boundInstances;
57 #region IController
59 /// <summary>
60 /// Occurs just before the action execution.
61 /// </summary>
62 public event ControllerHandler BeforeAction;
64 /// <summary>
65 /// Occurs just after the action execution.
66 /// </summary>
67 public event ControllerHandler AfterAction;
69 /// <summary>
70 /// Sets the context for the controller
71 /// </summary>
72 /// <param name="engineContext">The engine context.</param>
73 /// <param name="context">The controller context.</param>
74 public virtual void Contextualize(IEngineContext engineContext, IControllerContext context)
76 this.context = context;
77 SetEngineContext(engineContext);
78 ResolveLayout();
79 CreateAndInitializeHelpers();
80 CreateFiltersDescriptors();
81 ProcessScaffoldIfAvailable();
82 ActionProviderUtil.RegisterActions(engineContext, this, context);
83 Initialize();
84 isContextualized = true;
87 /// <summary>
88 /// Performs the specified action, which means:
89 /// <br/>
90 /// 1. Define the default view name<br/>
91 /// 2. Run the before filters<br/>
92 /// 3. Select the method related to the action name and invoke it<br/>
93 /// 4. On error, execute the rescues if available<br/>
94 /// 5. Run the after filters<br/>
95 /// 6. Invoke the view engine<br/>
96 /// </summary>
97 /// <param name="engineContext">The engine context.</param>
98 /// <param name="context">The controller context.</param>
99 public virtual void Process(IEngineContext engineContext, IControllerContext context)
101 if (isContextualized == false)
103 Contextualize(engineContext, context);
105 RunActionAndRenderView();
108 /// <summary>
109 /// Invoked by the view engine to perform
110 /// any logic before the view is sent to the client.
111 /// </summary>
112 /// <param name="view"></param>
113 public virtual void PreSendView(object view)
117 /// <summary>
118 /// Invoked by the view engine to perform
119 /// any logic after the view had been sent to the client.
120 /// </summary>
121 /// <param name="view"></param>
122 public virtual void PostSendView(object view)
126 /// <summary>
127 /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
128 /// </summary>
129 public virtual void Dispose()
131 DisposeFilters();
134 #endregion
136 #region IValidatorAccessor
138 /// <summary>
139 /// Gets the validator runner instance.
140 /// </summary>
141 /// <value>The validator instance.</value>
142 public ValidatorRunner Validator
146 if (validatorRunner == null)
148 validatorRunner = CreateValidatorRunner(engineContext.Services.ValidatorRegistry);
150 return validatorRunner;
152 set { validatorRunner = value; }
155 /// <summary>
156 /// Gets the bound instance errors. These are errors relative to
157 /// the binding process performed for the specified instance.
158 /// </summary>
159 /// <value>The bound instance errors.</value>
160 public IDictionary<object, ErrorList> BoundInstanceErrors
164 if (boundInstances == null)
166 boundInstances = new Dictionary<object, ErrorList>();
168 return boundInstances;
172 /// <summary>
173 /// Populates the validator error summary with errors relative to the
174 /// validation rules associated with the target type.
175 /// </summary>
176 /// <param name="instance">The instance.</param>
177 /// <param name="binderUsedForBinding">The binder used for binding.</param>
178 public void PopulateValidatorErrorSummary(object instance, ErrorSummary binderUsedForBinding)
180 if (validationSummaryPerInstance == null)
182 validationSummaryPerInstance = new Dictionary<object, ErrorSummary>();
184 validationSummaryPerInstance[instance] = binderUsedForBinding;
187 /// <summary>
188 /// Gets the error summary associated with validation errors.
189 /// <para>
190 /// Will only work for instances populated by the <c>DataBinder</c>
191 /// </para>
192 /// </summary>
193 /// <param name="instance">object instance</param>
194 /// <returns>Error summary instance (can be null if the DataBinder wasn't configured to validate)</returns>
195 protected ErrorSummary GetErrorSummary(object instance)
197 if (validationSummaryPerInstance == null)
199 return null;
201 return validationSummaryPerInstance.ContainsKey(instance) ? validationSummaryPerInstance[instance] : null;
204 /// <summary>
205 /// Returns <c>true</c> if the given instance had
206 /// validation errors during binding.
207 /// <para>
208 /// Will only work for instances populated by the <c>DataBinder</c>
209 /// </para>
210 /// </summary>
211 /// <param name="instance">object instance</param>
212 /// <returns><c>true</c> if the validation had an error</returns>
213 protected bool HasValidationError(object instance)
215 ErrorSummary summary = GetErrorSummary(instance);
217 if (summary == null)
219 return false;
222 return summary.ErrorsCount != 0;
225 /// <summary>
226 /// Gets a list of errors that were thrown during the
227 /// object process, like conversion errors.
228 /// </summary>
229 /// <param name="instance">The instance that was populated by a binder.</param>
230 /// <returns>List of errors</returns>
231 protected ErrorList GetDataBindErrors(object instance)
233 if (boundInstances != null && boundInstances.ContainsKey(instance))
235 return boundInstances[instance];
237 return null;
240 #endregion
242 #region Useful Properties
244 /// <summary>
245 /// Gets the controller context.
246 /// </summary>
247 /// <value>The controller context.</value>
248 public IControllerContext ControllerContext
250 get { return context; }
251 set { context = value; }
254 /// <summary>
255 /// Gets the view folder -- (areaname +
256 /// controllername) or just controller name -- that this controller
257 /// will use by default.
258 /// </summary>
259 public string ViewFolder
261 get { return context.ViewFolder; }
262 set { context.ViewFolder = value; }
265 /// <summary>
266 /// This is intended to be used by MonoRail infrastructure.
267 /// </summary>
268 public ControllerMetaDescriptor MetaDescriptor
270 get { return context.ControllerDescriptor; }
271 set { context.ControllerDescriptor = value; }
274 /// <summary>
275 /// Gets the actions available in this controller.
276 /// </summary>
277 /// <remarks>It is supposed to be used by MonoRail infrastructure only</remarks>
278 /// <value>The actions.</value>
279 public ICollection Actions
281 get { return MetaDescriptor.Actions.Values; }
284 /// <summary>
285 /// Gets a dicitionary of name/<see cref="IResource"/>
286 /// </summary>
287 /// <remarks>It is supposed to be used by MonoRail infrastructure only</remarks>
288 /// <value>The resources.</value>
289 public IDictionary<string, IResource> Resources
291 get { return context.Resources; }
294 /// <summary>
295 /// Gets a dictionary of name/helper instance
296 /// </summary>
297 /// <value>The helpers.</value>
298 public IDictionary Helpers
300 get { return context.Helpers; }
303 /// <summary>
304 /// Gets a value indicating whether the request is a post.
305 /// </summary>
306 /// <value>
307 /// <see langword="true"/> if this request is a post; otherwise, <see langword="false"/>.
308 /// </value>
309 public bool IsPost
311 get { return engineContext.Request.HttpMethod == "POST"; }
314 /// <summary>
315 /// Gets a value indicating whether the request is a get.
316 /// </summary>
317 /// <value>
318 /// <see langword="true"/> if this request is a get; otherwise, <see langword="false"/>.
319 /// </value>
320 public bool IsGet
322 get { return engineContext.Request.HttpMethod == "GET"; }
325 /// <summary>
326 /// Gets a value indicating whether the request is a put.
327 /// </summary>
328 /// <value>
329 /// <see langword="true"/> if this request is a put; otherwise, <see langword="false"/>.
330 /// </value>
331 public bool IsPut
333 get { return engineContext.Request.HttpMethod == "PUT"; }
336 /// <summary>
337 /// Gets a value indicating whether the request is a head.
338 /// </summary>
339 /// <value>
340 /// <see langword="true"/> if this request is a head; otherwise, <see langword="false"/>.
341 /// </value>
342 public bool IsHead
344 get { return engineContext.Request.HttpMethod == "HEAD"; }
347 /// <summary>
348 /// Gets the controller's name.
349 /// </summary>
350 public string Name
352 get { return context.Name; }
355 /// <summary>
356 /// Gets the controller's area name.
357 /// </summary>
358 public string AreaName
360 get { return context.AreaName; }
363 /// <summary>
364 /// Gets or set the layout being used.
365 /// </summary>
366 public string LayoutName
368 get { return (context.LayoutNames != null && context.LayoutNames.Length != 0) ? context.LayoutNames[0] : null; }
371 if (value == null)
373 context.LayoutNames = null;
375 else
377 context.LayoutNames = new string[] { value };
382 /// <summary>
383 /// Gets or set the layouts being used.
384 /// </summary>
385 public string[] LayoutNames
387 get { return context.LayoutNames; }
388 set { context.LayoutNames = value; }
391 /// <summary>
392 /// Gets the name of the action being processed.
393 /// </summary>
394 public string Action
396 get { return context.Action; }
399 /// <summary>
400 /// Logger for the controller
401 /// </summary>
402 public ILogger Logger
404 get { return logger; }
405 set { logger = value; }
408 /// <summary>
409 /// Gets or sets the view which will be rendered by this action.
410 /// </summary>
411 public string SelectedViewName
413 get { return context.SelectedViewName; }
414 set { context.SelectedViewName = value; }
417 /// <summary>
418 /// Gets the property bag, which is used
419 /// to pass variables to the view.
420 /// </summary>
421 public IDictionary PropertyBag
423 get { return context.PropertyBag; }
424 set { context.PropertyBag = value; }
427 /// <summary>
428 /// Gets the context of this request execution.
429 /// </summary>
430 public IEngineContext Context
432 get { return engineContext; }
435 /// <summary>
436 /// Gets the Session dictionary.
437 /// </summary>
438 protected IDictionary Session
440 get { return engineContext.Session; }
443 /// <summary>
444 /// Gets a dictionary of volative items.
445 /// Ideal for showing success and failures messages.
446 /// </summary>
447 public Flash Flash
449 get { return engineContext.Flash; }
452 /// <summary>
453 /// Gets the web context of ASP.NET API.
454 /// </summary>
455 protected internal HttpContext HttpContext
457 get { return engineContext.UnderlyingContext; }
460 /// <summary>
461 /// Gets the request object.
462 /// </summary>
463 public IRequest Request
465 get { return Context.Request; }
468 /// <summary>
469 /// Gets the response object.
470 /// </summary>
471 public IResponse Response
473 get { return Context.Response; }
476 /// <summary>
477 /// Shortcut to <see cref="IRequest.Params"/>
478 /// </summary>
479 public NameValueCollection Params
481 get { return Request.Params; }
484 /// <summary>
485 /// Shortcut to <see cref="IRequest.Form"/>
486 /// </summary>
487 public NameValueCollection Form
489 get { return Request.Form; }
492 /// <summary>
493 /// Shortcut to <see cref="IRequest.QueryString"></see>
494 /// </summary>
495 public NameValueCollection Query
497 get { return Request.QueryString; }
500 /// <summary>
501 /// Gets the dynamic actions dictionary.
502 /// <para>
503 /// Can be used to insert dynamic actions on the controller instance.
504 /// </para>
505 /// </summary>
506 /// <value>The dynamic actions dictionary.</value>
507 public IDictionary<string, IDynamicAction> DynamicActions
509 get { return context.DynamicActions; }
512 /// <summary>
513 /// Shortcut to
514 /// <see cref="IResponse.IsClientConnected"/>
515 /// </summary>
516 protected bool IsClientConnected
518 get { return engineContext.Response.IsClientConnected; }
521 /// <summary>
522 /// Indicates that the current Action resulted from an ASP.NET PostBack.
523 /// As a result, this property is only relavent to controllers using
524 /// WebForms views. It is placed on the base Controller for convenience
525 /// only to avoid the need to extend the Controller or provide additional
526 /// helper classes. It is marked virtual to better support testing.
527 /// </summary>
528 protected virtual bool IsPostBack
532 NameValueCollection fields = Params;
533 return (fields["__VIEWSTATE"] != null) || (fields["__EVENTTARGET"] != null);
537 #endregion
539 #region Useful Operations
541 /// <summary>
542 /// Sets the engine context. Also initialize all required services by querying
543 /// <see cref="IEngineContext.Services"/>
544 /// </summary>
545 /// <param name="engineContext">The engine context.</param>
546 public virtual void SetEngineContext(IEngineContext engineContext)
548 this.engineContext = engineContext;
550 helperFactory = engineContext.Services.HelperFactory; // should not be null
551 serviceInitializer = engineContext.Services.ServiceInitializer; // should not be null
552 // urlBuilder = engineContext.Services.UrlBuilder; // should not be null (affects redirects)
553 filterFactory = engineContext.Services.FilterFactory; // should not be null
554 viewEngineManager = engineContext.Services.ViewEngineManager; // should not be null
555 actionSelector = engineContext.Services.ActionSelector; // should not be null
556 scaffoldSupport = engineContext.Services.ScaffoldSupport; // might be null
559 /// <summary>
560 /// Specifies the view to be processed after the action has finished its processing.
561 /// </summary>
562 /// <param name="name">view template name (the file extension is optional)</param>
563 public void RenderView(string name)
565 context.SelectedViewName = Path.Combine(ViewFolder, name);
568 /// <summary>
569 /// Specifies the view to be processed after the action has finished its processing.
570 /// </summary>
571 /// <param name="name">view template name (the file extension is optional)</param>
572 /// <param name="skipLayout">If set to <c>true</c>, no layout will be used when rendering the view</param>
573 public void RenderView(string name, bool skipLayout)
575 if (skipLayout) CancelLayout();
577 RenderView(name);
580 /// <summary>
581 /// Specifies the view to be processed after the action has finished its processing.
582 /// </summary>
583 /// <param name="name">view template name (the file extension is optional)</param>
584 /// <param name="skipLayout">If set to <c>true</c>, no layout will be used when rendering the view</param>
585 /// <param name="mimeType">The mime type to use on the reply</param>
586 public void RenderView(string name, bool skipLayout, string mimeType)
588 if (skipLayout) CancelLayout();
589 Response.ContentType = mimeType;
591 RenderView(name);
594 /// <summary>
595 /// Specifies the view to be processed after the action has finished its processing.
596 /// </summary>
597 /// <param name="controller">Controller name get view from (if you intend to user another controller's view</param>
598 /// <param name="name">view template name (the file extension is optional)</param>
599 public void RenderView(string controller, string name)
601 context.SelectedViewName = Path.Combine(controller, name);
604 /// <summary>
605 /// Specifies the view to be processed after the action has finished its processing.
606 /// </summary>
607 /// <param name="controller">Controller name get view from (if you intend to user another controller's view</param>
608 /// <param name="name">view template name (the file extension is optional)</param>
609 /// <param name="skipLayout">If set to <c>true</c>, no layout will be used when rendering the view</param>
610 public void RenderView(string controller, string name, bool skipLayout)
612 if (skipLayout) CancelLayout();
614 RenderView(controller, name);
617 /// <summary>
618 /// Specifies the view to be processed after the action has finished its processing.
619 /// </summary>
620 /// <param name="controller">Controller name get view from (if you intend to user another controller's view</param>
621 /// <param name="name">view template name (the file extension is optional)</param>
622 /// <param name="skipLayout">If set to <c>true</c>, no layout will be used when rendering the view</param>
623 /// <param name="mimeType">The mime type to use on the reply</param>
624 public void RenderView(string controller, string name, bool skipLayout, string mimeType)
626 if (skipLayout) CancelLayout();
628 Response.ContentType = mimeType;
629 RenderView(controller, name);
632 /// <summary>
633 /// Specifies the view to be processed after the action has finished its processing.
634 /// </summary>
635 /// <param name="controller">Controller name get view from (if you intend to user another controller's view</param>
636 /// <param name="name">view template name (the file extension is optional)</param>
637 /// <param name="mimeType">The mime type to use on the reply</param>
638 public void RenderView(string controller, string name, string mimeType)
640 Response.ContentType = mimeType;
641 RenderView(controller, name);
644 /// <summary>
645 /// Specifies the view to be processed and results are written to System.IO.TextWriter.
646 /// </summary>
647 /// <param name="output"></param>
648 /// <param name="name">The name of the view to process.</param>
649 public void InPlaceRenderView(TextWriter output, string name)
651 viewEngineManager.Process(Path.Combine(ViewFolder, name), output, Context, this, context);
654 /// <summary>
655 /// Specifies the shared view to be processed after the action has finished its
656 /// processing. (A partial view shared
657 /// by others views and usually in the root folder
658 /// of the view directory).
659 /// </summary>
660 public void RenderSharedView(string name)
662 context.SelectedViewName = name;
665 /// <summary>
666 /// Specifies the shared view to be processed after the action has finished its
667 /// processing. (A partial view shared
668 /// by others views and usually in the root folder
669 /// of the view directory).
670 /// </summary>
671 public void RenderSharedView(string name, bool skipLayout)
673 if (skipLayout) CancelLayout();
675 RenderSharedView(name);
678 /// <summary>
679 /// Specifies the shared view to be processed and results are written to System.IO.TextWriter.
680 /// (A partial view shared by others views and usually in the root folder
681 /// of the view directory).
682 /// </summary>
683 /// <param name="output"></param>
684 /// <param name="name">The name of the view to process.</param>
685 public void InPlaceRenderSharedView(TextWriter output, string name)
687 viewEngineManager.Process(name, output, Context, this, context);
690 /// <summary>
691 /// Cancels the view processing.
692 /// </summary>
693 public void CancelView()
695 context.SelectedViewName = null;
698 /// <summary>
699 /// Cancels the layout processing.
700 /// </summary>
701 public void CancelLayout()
703 LayoutName = null;
706 /// <summary>
707 /// Cancels the view processing and writes
708 /// the specified contents to the browser
709 /// </summary>
710 public void RenderText(string contents)
712 CancelView();
714 engineContext.Response.Write(contents);
717 /// <summary>
718 /// Cancels the view processing and writes
719 /// the specified contents to the browser
720 /// </summary>
721 public void RenderText(string contents, params object[] args)
723 RenderText(String.Format(contents, args));
726 /// <summary>
727 /// Cancels the view processing and writes
728 /// the specified contents to the browser
729 /// </summary>
730 public void RenderText(IFormatProvider formatProvider, string contents, params object[] args)
732 RenderText(String.Format(formatProvider, contents, args));
735 /// <summary>
736 /// Sends raw contents to be rendered directly by the view engine.
737 /// It's up to the view engine just to apply the layout and nothing else.
738 /// </summary>
739 /// <param name="contents">Contents to be rendered.</param>
740 public void DirectRender(string contents)
742 CancelView();
744 if (directRenderInvoked)
746 throw new ControllerException("DirectRender should be called only once.");
749 directRenderInvoked = true;
751 viewEngineManager.RenderStaticWithinLayout(contents, engineContext, this, context);
754 /// <summary>
755 /// Returns true if the specified template exists.
756 /// </summary>
757 /// <param name="templateName"></param>
758 public bool HasTemplate(string templateName)
760 return viewEngineManager.HasTemplate(templateName);
763 #region Redirects
765 /// <summary>
766 /// Redirects to another action in the same controller.
767 /// </summary>
768 /// <param name="action">The action name</param>
769 public void RedirectToAction(string action)
771 RedirectToAction(action, (NameValueCollection) null);
774 /// <summary>
775 /// Redirects to another action in the same controller passing the specified querystring parameters.
776 /// </summary>
777 /// <param name="action">The action name</param>
778 /// <param name="queryStringParameters">The querystring entries</param>
779 public void RedirectToAction(string action, IDictionary queryStringParameters)
781 if (queryStringParameters != null)
783 Response.Redirect(AreaName, Name, TransformActionName(action), queryStringParameters);
785 else
787 Response.Redirect(AreaName, Name, TransformActionName(action));
791 /// <summary>
792 /// Redirects to another action in the same controller passing the specified querystring parameters.
793 /// </summary>
794 /// <param name="action">The action name</param>
795 /// <param name="queryStringParameters">The querystring entries</param>
796 public void RedirectToAction(string action, NameValueCollection queryStringParameters)
798 if (queryStringParameters != null)
800 Response.Redirect(AreaName, Name, TransformActionName(action), queryStringParameters);
802 else
804 Response.Redirect(AreaName, Name, TransformActionName(action));
808 /// <summary>
809 /// Redirects to another action in the same controller passing the specified querystring parameters.
810 /// </summary>
811 /// <param name="action">The action name</param>
812 /// <param name="queryStringParameters">The querystring entries</param>
813 public void RedirectToAction(string action, params string[] queryStringParameters)
815 RedirectToAction(action, DictHelper.Create(queryStringParameters));
818 /// <summary>
819 /// Redirects to another action in the same controller passing the specified querystring parameters.
820 /// </summary>
821 /// <param name="action">The action name</param>
822 /// <param name="queryStringAnonymousDictionary">The querystring entries as an anonymous dictionary</param>
823 public void RedirectToAction(string action, object queryStringAnonymousDictionary)
825 RedirectToAction(action, new ReflectionBasedDictionaryAdapter(queryStringAnonymousDictionary));
828 /// <summary>
829 /// Redirects to url using referrer.
830 /// </summary>
831 public void RedirectToReferrer()
833 Response.RedirectToReferrer();
836 /// <summary>
837 /// Redirects to the site root directory (<c>Context.ApplicationPath + "/"</c>).
838 /// </summary>
839 public void RedirectToSiteRoot()
841 Response.RedirectToSiteRoot();
844 /// <summary>
845 /// Redirects to the specified url
846 /// </summary>
847 /// <param name="url">An relative or absolute URL to redirect the client to</param>
848 public void RedirectToUrl(string url)
850 Response.RedirectToUrl(url);
853 /// <summary>
854 /// Redirects to the specified url
855 /// </summary>
856 /// <param name="url">An relative or absolute URL to redirect the client to</param>
857 /// <param name="endProcess">if set to <c>true</c>, sends the redirect and
858 /// kills the current request process.</param>
859 public void RedirectToUrl(string url, bool endProcess)
861 Response.RedirectToUrl(url, endProcess);
864 /// <summary>
865 /// Redirects to the specified url
866 /// </summary>
867 /// <param name="url">An relative or absolute URL to redirect the client to</param>
868 /// <param name="queryStringParameters">The querystring entries</param>
869 public void RedirectToUrl(string url, IDictionary queryStringParameters)
871 Response.RedirectToUrl(url, queryStringParameters);
874 /// <summary>
875 /// Redirects to the specified url
876 /// </summary>
877 /// <param name="url">An relative or absolute URL to redirect the client to</param>
878 /// <param name="queryStringParameters">The querystring entries</param>
879 public void RedirectToUrl(string url, NameValueCollection queryStringParameters)
881 Response.RedirectToUrl(url, queryStringParameters);
884 /// <summary>
885 /// Redirects to the specified url
886 /// </summary>
887 /// <param name="url">An relative or absolute URL to redirect the client to</param>
888 /// <param name="queryStringParameters">The querystring entries</param>
889 public void RedirectToUrl(string url, params string[] queryStringParameters)
891 Response.RedirectToUrl(url, queryStringParameters);
894 /// <summary>
895 /// Redirects to the specified url
896 /// </summary>
897 /// <param name="url">An relative or absolute URL to redirect the client to</param>
898 /// <param name="queryStringAnonymousDictionary">The querystring entries as an anonymous dictionary</param>
899 public void RedirectToUrl(string url, object queryStringAnonymousDictionary)
901 Response.RedirectToUrl(url, queryStringAnonymousDictionary);
904 /// <summary>
905 /// Redirects to another controller's action.
906 /// </summary>
907 /// <param name="controller">The controller name to be redirected to.</param>
908 /// <param name="action">The desired action on the target controller.</param>
909 public void Redirect(string controller, string action)
911 Response.Redirect(controller, action);
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="queryStringParameters">The querystring entries</param>
920 public void Redirect(string controller, string action, NameValueCollection queryStringParameters)
922 Response.Redirect(controller, action, queryStringParameters);
925 /// <summary>
926 /// Redirects to another controller's action with the specified parameters.
927 /// </summary>
928 /// <param name="controller">The controller name to be redirected to.</param>
929 /// <param name="action">The desired action on the target controller.</param>
930 /// <param name="queryStringParameters">The querystring entries</param>
931 public void Redirect(string controller, string action, IDictionary queryStringParameters)
933 Response.Redirect(controller, action, queryStringParameters);
936 /// <summary>
937 /// Redirects to another controller's action with the specified parameters.
938 /// </summary>
939 /// <param name="controller">The controller name to be redirected to.</param>
940 /// <param name="action">The desired action on the target controller.</param>
941 /// <param name="queryStringAnonymousDictionary">The querystring entries as an anonymous dictionary</param>
942 public void Redirect(string controller, string action, object queryStringAnonymousDictionary)
944 Response.Redirect(controller, action, queryStringAnonymousDictionary);
947 /// <summary>
948 /// Redirects to another controller's action in a different area.
949 /// </summary>
950 /// <param name="area">The area the target controller belongs to.</param>
951 /// <param name="controller">The controller name to be redirected to.</param>
952 /// <param name="action">The desired action on the target controller.</param>
953 public void Redirect(string area, string controller, string action)
955 Response.Redirect(area, controller, action);
958 /// <summary>
959 /// Redirects to another controller's action in a different area with the specified parameters.
960 /// </summary>
961 /// <param name="area">The area the target controller belongs to.</param>
962 /// <param name="controller">The controller name to be redirected to.</param>
963 /// <param name="action">The desired action on the target controller.</param>
964 /// <param name="queryStringParameters">The querystring entries</param>
965 public void Redirect(string area, string controller, string action, IDictionary queryStringParameters)
967 Response.Redirect(area, controller, action, queryStringParameters);
970 /// <summary>
971 /// Redirects to another controller's action in a different area with the specified parameters.
972 /// </summary>
973 /// <param name="area">The area the target controller belongs to.</param>
974 /// <param name="controller">The controller name to be redirected to.</param>
975 /// <param name="action">The desired action on the target controller.</param>
976 /// <param name="queryStringParameters">The querystring entries</param>
977 public void Redirect(string area, string controller, string action, NameValueCollection queryStringParameters)
979 Response.Redirect(area, controller, action, queryStringParameters);
982 /// <summary>
983 /// Redirects to another controller's action in a different area with the specified parameters.
984 /// </summary>
985 /// <param name="area">The area the target controller belongs to.</param>
986 /// <param name="controller">The controller name to be redirected to.</param>
987 /// <param name="action">The desired action on the target controller.</param>
988 /// <param name="queryStringAnonymousDictionary">The querystring entries as an anonymous dictionary</param>
989 public void Redirect(string area, string controller, string action, object queryStringAnonymousDictionary)
991 Response.Redirect(area, controller, action, queryStringAnonymousDictionary);
994 /// <summary>
995 /// Redirects using a named route.
996 /// The name must exists otherwise a <see cref="MonoRailException"/> will be thrown.
997 /// </summary>
998 /// <param name="routeName">Route name.</param>
999 public void RedirectUsingNamedRoute(string routeName)
1001 Response.RedirectUsingNamedRoute(routeName);
1004 /// <summary>
1005 /// Redirects using a named route.
1006 /// The name must exists otherwise a <see cref="MonoRailException"/> will be thrown.
1007 /// </summary>
1008 /// <param name="routeName">Route name.</param>
1009 /// <param name="routeParameters">The route parameters.</param>
1010 public void RedirectUsingNamedRoute(string routeName, object routeParameters)
1012 Response.RedirectUsingNamedRoute(routeName, routeParameters);
1015 /// <summary>
1016 /// Redirects using a named route.
1017 /// The name must exists otherwise a <see cref="MonoRailException"/> will be thrown.
1018 /// </summary>
1019 /// <param name="routeName">Route name.</param>
1020 /// <param name="routeParameters">The route parameters.</param>
1021 public void RedirectUsingNamedRoute(string routeName, IDictionary routeParameters)
1023 Response.RedirectUsingNamedRoute(routeName, routeParameters);
1026 /// <summary>
1027 /// Tries to resolve the target redirect url by using the routing rules registered.
1028 /// </summary>
1029 /// <param name="action">The desired action on the target controller.</param>
1030 /// <param name="useCurrentRouteParams">if set to <c>true</c> the current request matching route rules will be used.</param>
1031 public void RedirectUsingRoute(string action, bool useCurrentRouteParams)
1033 Response.RedirectUsingRoute(Name, action, useCurrentRouteParams);
1036 /// <summary>
1037 /// Tries to resolve the target redirect url by using the routing rules registered.
1038 /// </summary>
1039 /// <param name="action">The desired action on the target controller.</param>
1040 /// <param name="routeParameters">The routing rule parameters.</param>
1041 public void RedirectUsingRoute(string action, IDictionary routeParameters)
1043 Response.RedirectUsingRoute(Name, action, routeParameters);
1046 /// <summary>
1047 /// Tries to resolve the target redirect url by using the routing rules registered.
1048 /// </summary>
1049 /// <param name="action">The desired action on the target controller.</param>
1050 /// <param name="routeParameters">The routing rule parameters.</param>
1051 public void RedirectUsingRoute(string action, object routeParameters)
1053 Response.RedirectUsingRoute(Name, action, routeParameters);
1056 /// <summary>
1057 /// Tries to resolve the target redirect url by using the routing rules registered.
1058 /// </summary>
1059 /// <param name="controller">The controller name to be redirected to.</param>
1060 /// <param name="action">The desired action on the target controller.</param>
1061 /// <param name="useCurrentRouteParams">if set to <c>true</c> the current request matching route rules will be used.</param>
1062 public void RedirectUsingRoute(string controller, string action, bool useCurrentRouteParams)
1064 Response.RedirectUsingRoute(controller, action, useCurrentRouteParams);
1067 /// <summary>
1068 /// Tries to resolve the target redirect url by using the routing rules registered.
1069 /// </summary>
1070 /// <param name="area">The area the target controller belongs to.</param>
1071 /// <param name="controller">The controller name to be redirected to.</param>
1072 /// <param name="action">The desired action on the target controller.</param>
1073 /// <param name="useCurrentRouteParams">if set to <c>true</c> the current request matching route rules will be used.</param>
1074 public void RedirectUsingRoute(string area, string controller, string action, bool useCurrentRouteParams)
1076 Response.RedirectUsingRoute(area, controller, action, useCurrentRouteParams);
1079 /// <summary>
1080 /// Tries to resolve the target redirect url by using the routing rules registered.
1081 /// </summary>
1082 /// <param name="controller">The controller name to be redirected to.</param>
1083 /// <param name="action">The desired action on the target controller.</param>
1084 /// <param name="routeParameters">The routing rule parameters.</param>
1085 public void RedirectUsingRoute(string controller, string action, IDictionary routeParameters)
1087 Response.RedirectUsingRoute(controller, action, routeParameters);
1090 /// <summary>
1091 /// Tries to resolve the target redirect url by using the routing rules registered.
1092 /// </summary>
1093 /// <param name="controller">The controller name to be redirected to.</param>
1094 /// <param name="action">The desired action on the target controller.</param>
1095 /// <param name="routeParameters">The routing rule parameters.</param>
1096 public void RedirectUsingRoute(string controller, string action, object routeParameters)
1098 Response.RedirectUsingRoute(controller, action, routeParameters);
1101 /// <summary>
1102 /// Tries to resolve the target redirect url by using the routing rules registered.
1103 /// </summary>
1104 /// <param name="area">The area the target controller belongs to.</param>
1105 /// <param name="controller">The controller name to be redirected to.</param>
1106 /// <param name="action">The desired action on the target controller.</param>
1107 /// <param name="routeParameters">The routing rule parameters.</param>
1108 public void RedirectUsingRoute(string area, string controller, string action, IDictionary routeParameters)
1110 Response.RedirectUsingRoute(area, controller, action, routeParameters);
1113 /// <summary>
1114 /// Tries to resolve the target redirect url by using the routing rules registered.
1115 /// </summary>
1116 /// <param name="area">The area the target controller belongs to.</param>
1117 /// <param name="controller">The controller name to be redirected to.</param>
1118 /// <param name="action">The desired action on the target controller.</param>
1119 /// <param name="routeParameters">The routing rule parameters.</param>
1120 public void RedirectUsingRoute(string area, string controller, string action, object routeParameters)
1122 Response.RedirectUsingRoute(area, controller, action, routeParameters);
1125 #endregion
1127 #region Redirect utilities
1129 /// <summary>
1130 /// Creates a querystring string representation of the namevalue collection.
1131 /// </summary>
1132 /// <param name="parameters">The parameters.</param>
1133 /// <returns></returns>
1134 protected string ToQueryString(NameValueCollection parameters)
1136 return CommonUtils.BuildQueryString(Context.Server, parameters, false);
1139 /// <summary>
1140 /// Creates a querystring string representation of the entries in the dictionary.
1141 /// </summary>
1142 /// <param name="parameters">The parameters.</param>
1143 /// <returns></returns>
1144 protected string ToQueryString(IDictionary parameters)
1146 return CommonUtils.BuildQueryString(Context.Server, parameters, false);
1149 #endregion
1151 #endregion
1153 #region Email operations
1155 /// <summary>
1156 /// Creates an instance of <see cref="Message"/>
1157 /// using the specified template for the body
1158 /// </summary>
1159 /// <param name="templateName">
1160 /// Name of the template to load.
1161 /// Will look in Views/mail for that template file.
1162 /// </param>
1163 /// <returns>An instance of <see cref="Message"/></returns>
1164 [Obsolete]
1165 public Message RenderMailMessage(string templateName)
1167 return RenderMailMessage(templateName, false);
1170 /// <summary>
1171 /// Creates an instance of <see cref="Message"/>
1172 /// using the specified template for the body
1173 /// </summary>
1174 /// <param name="templateName">
1175 /// Name of the template to load.
1176 /// Will look in Views/mail for that template file.
1177 /// </param>
1178 /// <param name="doNotApplyLayout">If <c>true</c>, it will skip the layout</param>
1179 /// <returns>An instance of <see cref="Message"/></returns>
1180 [Obsolete]
1181 public Message RenderMailMessage(string templateName, bool doNotApplyLayout)
1183 IEmailTemplateService templateService = engineContext.Services.EmailTemplateService;
1184 return templateService.RenderMailMessage(templateName, Context, this, ControllerContext, doNotApplyLayout);
1187 /// <summary>
1188 /// Creates an instance of <see cref="Message"/>
1189 /// using the specified template for the body
1190 /// </summary>
1191 /// <param name="templateName">Name of the template to load.
1192 /// Will look in Views/mail for that template file.</param>
1193 /// <param name="layoutName">Name of the layout.</param>
1194 /// <param name="parameters">The parameters.</param>
1195 /// <returns>An instance of <see cref="Message"/></returns>
1196 public Message RenderMailMessage(string templateName, string layoutName, IDictionary parameters)
1198 IEmailTemplateService templateService = engineContext.Services.EmailTemplateService;
1199 return templateService.RenderMailMessage(templateName, layoutName, parameters);
1202 /// <summary>
1203 /// Creates an instance of <see cref="Message"/>
1204 /// using the specified template for the body
1205 /// </summary>
1206 /// <param name="templateName">Name of the template to load.
1207 /// Will look in Views/mail for that template file.</param>
1208 /// <param name="layoutName">Name of the layout.</param>
1209 /// <param name="parameters">The parameters.</param>
1210 /// <returns>An instance of <see cref="Message"/></returns>
1211 public Message RenderMailMessage(string templateName, string layoutName, IDictionary<string, object> parameters)
1213 IEmailTemplateService templateService = engineContext.Services.EmailTemplateService;
1214 return templateService.RenderMailMessage(templateName, layoutName, parameters);
1217 /// <summary>
1218 /// Creates an instance of <see cref="Message"/>
1219 /// using the specified template for the body
1220 /// </summary>
1221 /// <param name="templateName">Name of the template to load.
1222 /// Will look in Views/mail for that template file.</param>
1223 /// <param name="layoutName">Name of the layout.</param>
1224 /// <param name="parameters">The parameters.</param>
1225 /// <returns>An instance of <see cref="Message"/></returns>
1226 public Message RenderMailMessage(string templateName, string layoutName, object parameters)
1228 IEmailTemplateService templateService = engineContext.Services.EmailTemplateService;
1229 return templateService.RenderMailMessage(templateName, layoutName, parameters);
1232 /// <summary>
1233 /// Attempts to deliver the Message using the server specified on the web.config.
1234 /// </summary>
1235 /// <param name="message">The instance of System.Web.Mail.MailMessage that will be sent</param>
1236 public void DeliverEmail(Message message)
1240 IEmailSender sender = engineContext.Services.EmailSender;
1241 sender.Send(message);
1243 catch(Exception ex)
1245 if (logger.IsErrorEnabled)
1247 logger.Error("Error sending e-mail", ex);
1250 throw new MonoRailException("Error sending e-mail", ex);
1254 /// <summary>
1255 /// Renders and delivers the e-mail message.
1256 /// <seealso cref="DeliverEmail"/>
1257 /// </summary>
1258 /// <param name="templateName"></param>
1259 [Obsolete]
1260 public void RenderEmailAndSend(string templateName)
1262 Message message = RenderMailMessage(templateName);
1263 DeliverEmail(message);
1266 #endregion
1268 #region Lifecycle (overridables)
1270 /// <summary>
1271 /// Initializes this instance. Implementors
1272 /// can use this method to perform initialization
1273 /// </summary>
1274 public virtual void Initialize()
1278 #endregion
1280 #region Resources/i18n
1282 /// <summary>
1283 /// Creates the controller level resources.
1284 /// </summary>
1285 protected virtual void CreateControllerLevelResources()
1287 CreateResources(MetaDescriptor.Resources);
1290 /// <summary>
1291 /// Creates the controller level resources.
1292 /// </summary>
1293 /// <param name="action">The action.</param>
1294 protected virtual void CreateActionLevelResources(IExecutableAction action)
1296 CreateResources(action.Resources);
1299 /// <summary>
1300 /// Creates the resources and adds them to the <see cref="IControllerContext.Resources"/>.
1301 /// </summary>
1302 /// <param name="resources">The resources.</param>
1303 protected virtual void CreateResources(ResourceDescriptor[] resources)
1305 if (resources == null || resources.Length == 0)
1307 return;
1310 Assembly typeAssembly = GetType().Assembly;
1312 IResourceFactory resourceFactory = engineContext.Services.ResourceFactory;
1314 foreach(ResourceDescriptor resDesc in resources)
1316 if (ControllerContext.Resources.ContainsKey(resDesc.Name))
1318 throw new MonoRailException("There is a duplicated entry on the resource dictionary. Resource entry name: " +
1319 resDesc.Name);
1322 ControllerContext.Resources.Add(resDesc.Name, resourceFactory.Create(resDesc, typeAssembly));
1326 #endregion
1328 /// <summary>
1329 /// Gives a change to subclass
1330 /// to override the layout resolution code
1331 /// </summary>
1332 protected virtual void ResolveLayout()
1334 context.LayoutNames = ObtainDefaultLayoutName();
1337 private void RunActionAndRenderView()
1339 IExecutableAction action = null;
1340 Exception actionException = null;
1341 bool cancel;
1345 action = SelectAction(Action);
1347 if (action == null)
1349 throw new MonoRailException(404, "Not Found", "Could not find action named " +
1350 Action + " on controller " + AreaName + "\\" + Name);
1353 EnsureActionIsAccessibleWithCurrentHttpVerb(action);
1355 RunBeforeActionFilters(action, out cancel);
1357 CreateControllerLevelResources();
1358 CreateActionLevelResources(action);
1360 if (cancel) return;
1362 if (BeforeAction != null)
1364 BeforeAction(action, engineContext, this, context);
1367 object actionRetValue = action.Execute(engineContext, this, context);
1369 // TO DO: review/refactor this code
1370 if (action.ReturnBinderDescriptor != null)
1372 IReturnBinder binder = action.ReturnBinderDescriptor.ReturnTypeBinder;
1374 // Runs return binder and keep going
1375 binder.Bind(Context, this, ControllerContext, action.ReturnBinderDescriptor.ReturnType, actionRetValue);
1378 // Action executed successfully, so it's safe to process the cache configurer
1379 if ((MetaDescriptor.CacheConfigurer != null || action.CachePolicyConfigurer != null) &&
1380 !Response.WasRedirected && Response.StatusCode == 200)
1382 ConfigureCachePolicy(action);
1385 catch(MonoRailException ex)
1387 if (Response.StatusCode == 200 && ex.HttpStatusCode.HasValue)
1389 Response.StatusCode = ex.HttpStatusCode.Value;
1390 Response.StatusDescription = ex.HttpStatusDesc;
1393 actionException = ex;
1395 RegisterExceptionAndNotifyExtensions(actionException);
1397 RunAfterActionFilters(action, out cancel);
1399 if (!ProcessRescue(action, actionException))
1401 throw;
1403 return;
1405 catch(Exception ex)
1407 if (Response.StatusCode == 200)
1409 Response.StatusCode = 500;
1410 Response.StatusDescription = "Error processing action";
1413 actionException = (ex is TargetInvocationException) ? ex.InnerException : ex;
1415 RegisterExceptionAndNotifyExtensions(actionException);
1417 RunAfterActionFilters(action, out cancel);
1419 if (!ProcessRescue(action, actionException))
1421 throw;
1423 return;
1425 finally
1427 // AfterAction event: always executed
1428 if (AfterAction != null)
1430 AfterAction(action, engineContext, this, context);
1434 RunAfterActionFilters(action, out cancel);
1435 if (cancel) return;
1437 if (engineContext.Response.WasRedirected) // No need to process view or rescue in this case
1439 return;
1442 if (context.SelectedViewName != null)
1444 ProcessView();
1445 RunAfterRenderingFilters(action);
1449 /// <summary>
1450 /// Configures the cache policy.
1451 /// </summary>
1452 /// <param name="action">The action.</param>
1453 protected virtual void ConfigureCachePolicy(IExecutableAction action)
1455 ICachePolicyConfigurer configurer = action.CachePolicyConfigurer ?? MetaDescriptor.CacheConfigurer;
1457 configurer.Configure(Response.CachePolicy);
1460 /// <summary>
1461 /// Selects the appropriate action.
1462 /// </summary>
1463 /// <param name="action">The action name.</param>
1464 /// <returns></returns>
1465 protected virtual IExecutableAction SelectAction(string action)
1467 // For backward compatibility purposes
1468 MethodInfo method = SelectMethod(action, MetaDescriptor.Actions, engineContext.Request, context.CustomActionParameters);
1470 if (method != null)
1472 ActionMetaDescriptor actionMeta = MetaDescriptor.GetAction(method);
1474 return new ActionMethodExecutorCompatible(method, actionMeta ?? new ActionMetaDescriptor(), InvokeMethod);
1477 // New supported way
1478 return actionSelector.Select(engineContext, this, context);
1481 /// <summary>
1482 /// Invokes the scaffold support if the controller
1483 /// is associated with a scaffold
1484 /// </summary>
1485 protected virtual void ProcessScaffoldIfAvailable()
1487 if (MetaDescriptor.Scaffoldings.Count != 0)
1489 if (scaffoldSupport == null)
1491 String message = "You must enable scaffolding support on the " +
1492 "configuration file, or, to use the standard ActiveRecord support " +
1493 "copy the necessary assemblies to the bin folder.";
1495 throw new MonoRailException(message);
1498 scaffoldSupport.Process(engineContext, this, context);
1502 /// <summary>
1503 /// Ensures the action is accessible with current HTTP verb.
1504 /// </summary>
1505 /// <param name="action">The action.</param>
1506 protected virtual void EnsureActionIsAccessibleWithCurrentHttpVerb(IExecutableAction action)
1508 Verb allowedVerbs = action.AccessibleThroughVerb;
1510 if (allowedVerbs == Verb.Undefined)
1512 return;
1515 string method = engineContext.Request.HttpMethod;
1517 Verb currentVerb = (Verb) Enum.Parse(typeof(Verb), method, true);
1519 if ((allowedVerbs & currentVerb) != currentVerb)
1521 throw new MonoRailException(403, "Forbidden",
1522 string.Format("Access to the action [{0}] " +
1523 "on controller [{1}] is not allowed to the http verb [{2}].",
1524 Action, Name, method));
1528 #region Views and Layout
1530 /// <summary>
1531 /// Obtains the name of the default layout.
1532 /// </summary>
1533 /// <returns></returns>
1534 protected virtual String[] ObtainDefaultLayoutName()
1536 if (MetaDescriptor.Layout != null)
1538 return MetaDescriptor.Layout.LayoutNames;
1540 else
1542 String defaultLayout = String.Format("layouts/{0}", Name);
1544 if (viewEngineManager.HasTemplate(defaultLayout))
1546 return new String[] {Name};
1550 return null;
1553 /// <summary>
1554 /// Processes the view.
1555 /// </summary>
1556 protected virtual void ProcessView()
1558 if (context.SelectedViewName != null)
1560 viewEngineManager.Process(context.SelectedViewName, engineContext.Response.Output, engineContext, this, context);
1564 #endregion
1566 #region Helpers
1568 /// <summary>
1569 /// Creates the and initialize helpers associated with a controller.
1570 /// </summary>
1571 public virtual void CreateAndInitializeHelpers()
1573 IDictionary helpers = context.Helpers;
1575 // Custom helpers
1577 foreach(HelperDescriptor helper in MetaDescriptor.Helpers)
1579 bool initialized;
1580 object helperInstance = helperFactory.Create(helper.HelperType, engineContext, out initialized);
1582 if (!initialized)
1584 serviceInitializer.Initialize(helperInstance, engineContext);
1587 if (helpers.Contains(helper.Name))
1589 throw new ControllerException(String.Format("Found a duplicate helper " +
1590 "attribute named '{0}' on controller '{1}'", helper.Name, Name));
1593 helpers.Add(helper.Name, helperInstance);
1596 CreateStandardHelpers();
1599 /// <summary>
1600 /// Creates the standard helpers.
1601 /// </summary>
1602 public virtual void CreateStandardHelpers()
1604 AbstractHelper[] builtInHelpers =
1605 new AbstractHelper[]
1607 new AjaxHelper(engineContext), new BehaviourHelper(engineContext),
1608 new UrlHelper(engineContext), new TextHelper(engineContext),
1609 new EffectsFatHelper(engineContext), new ScriptaculousHelper(engineContext),
1610 new DateFormatHelper(engineContext), new HtmlHelper(engineContext),
1611 new ValidationHelper(engineContext), new DictHelper(engineContext),
1612 new PaginationHelper(engineContext), new FormHelper(engineContext),
1613 new JSONHelper(engineContext), new ZebdaHelper(engineContext)
1616 foreach(AbstractHelper helper in builtInHelpers)
1618 context.Helpers.Add(helper);
1620 if (helper is IServiceEnabledComponent)
1622 serviceInitializer.Initialize(helper, engineContext);
1627 #endregion
1629 #region Filters
1631 private void CreateFiltersDescriptors()
1633 if (MetaDescriptor.Filters.Length != 0)
1635 filters = CopyFilterDescriptors();
1639 private void RunBeforeActionFilters(IExecutableAction action, out bool cancel)
1641 cancel = false;
1642 if (action.ShouldSkipAllFilters) return;
1644 if (!ProcessFilters(action, ExecuteWhen.BeforeAction))
1646 cancel = true;
1647 return; // A filter returned false... so we stop
1651 private void RunAfterActionFilters(IExecutableAction action, out bool cancel)
1653 cancel = false;
1654 if (action == null) return;
1656 if (action.ShouldSkipAllFilters) return;
1658 if (!ProcessFilters(action, ExecuteWhen.AfterAction))
1660 cancel = true;
1661 return; // A filter returned false... so we stop
1665 private void RunAfterRenderingFilters(IExecutableAction action)
1667 if (action.ShouldSkipAllFilters) return;
1669 ProcessFilters(action, ExecuteWhen.AfterRendering);
1672 /// <summary>
1673 /// Identifies if no filter should run for the given action.
1674 /// </summary>
1675 /// <param name="action">The action.</param>
1676 /// <returns></returns>
1677 protected virtual bool ShouldSkipFilters(IExecutableAction action)
1679 if (filters == null)
1681 // No filters, so skip
1682 return true;
1685 return action.ShouldSkipAllFilters;
1687 // ActionMetaDescriptor actionMeta = MetaDescriptor.GetAction(method);
1689 // if (actionMeta.SkipFilters.Count == 0)
1690 // {
1691 // // Nothing against filters declared for this action
1692 // return false;
1693 // }
1695 // foreach(SkipFilterAttribute skipfilter in actionMeta.SkipFilters)
1696 // {
1697 // // SkipAllFilters handling...
1698 // if (skipfilter.BlanketSkip)
1699 // {
1700 // return true;
1701 // }
1703 // filtersToSkip[skipfilter.FilterType] = String.Empty;
1704 // }
1706 // return false;
1709 /// <summary>
1710 /// Clones all Filter descriptors, in order to get a writable copy.
1711 /// </summary>
1712 protected internal FilterDescriptor[] CopyFilterDescriptors()
1714 FilterDescriptor[] clone = (FilterDescriptor[]) MetaDescriptor.Filters.Clone();
1716 for(int i = 0; i < clone.Length; i++)
1718 clone[i] = (FilterDescriptor) clone[i].Clone();
1721 return clone;
1724 private bool ProcessFilters(IExecutableAction action, ExecuteWhen when)
1726 foreach(FilterDescriptor desc in filters)
1728 if (action.ShouldSkipFilter(desc.FilterType))
1730 continue;
1733 if ((desc.When & when) != 0)
1735 if (!ProcessFilter(when, desc))
1737 return false;
1742 return true;
1745 private bool ProcessFilter(ExecuteWhen when, FilterDescriptor desc)
1747 if (desc.FilterInstance == null)
1749 desc.FilterInstance = filterFactory.Create(desc.FilterType);
1751 IFilterAttributeAware filterAttAware = desc.FilterInstance as IFilterAttributeAware;
1753 if (filterAttAware != null)
1755 filterAttAware.Filter = desc.Attribute;
1761 if (logger.IsDebugEnabled)
1763 logger.DebugFormat("Running filter {0}/{1}", when, desc.FilterType.FullName);
1766 return desc.FilterInstance.Perform(when, engineContext, this, context);
1768 catch(Exception ex)
1770 if (logger.IsErrorEnabled)
1772 logger.ErrorFormat("Error processing filter " + desc.FilterType.FullName, ex);
1775 throw;
1779 private void DisposeFilters()
1781 if (filters == null) return;
1783 foreach(FilterDescriptor desc in filters)
1785 if (desc.FilterInstance != null)
1787 filterFactory.Release(desc.FilterInstance);
1792 #endregion
1794 #region Rescue
1796 /// <summary>
1797 /// Performs the rescue.
1798 /// </summary>
1799 /// <param name="action">The action (can be null in the case of dynamic actions).</param>
1800 /// <param name="actionException">The exception.</param>
1801 /// <returns></returns>
1802 protected bool ProcessRescue(IExecutableAction action, Exception actionException)
1804 if (action != null && action.ShouldSkipRescues)
1806 return false;
1809 Type exceptionType = actionException.GetType();
1811 RescueDescriptor desc = action != null ? action.GetRescueFor(exceptionType) : null;
1813 if (desc == null)
1815 desc = GetControllerRescueFor(exceptionType);
1818 if (desc != null)
1822 if (desc.RescueController != null)
1824 CreateAndProcessRescueController(desc, actionException);
1826 else
1828 context.SelectedViewName = Path.Combine("rescues", desc.ViewName);
1830 ProcessView();
1833 return true;
1835 catch(Exception exception)
1837 // In this situation, the rescue view could not be found
1838 // So we're back to the default error exibition
1840 if (logger.IsFatalEnabled)
1842 logger.FatalFormat("Failed to process rescue view. View name " +
1843 context.SelectedViewName, exception);
1848 return false;
1851 /// <summary>
1852 /// Gets the best rescue that matches the exception type
1853 /// </summary>
1854 /// <param name="exceptionType">Type of the exception.</param>
1855 /// <returns></returns>
1856 protected virtual RescueDescriptor GetControllerRescueFor(Type exceptionType)
1858 return RescueUtils.SelectBest(MetaDescriptor.Rescues, exceptionType);
1861 private void CreateAndProcessRescueController(RescueDescriptor desc, Exception actionException)
1863 IController rescueController = engineContext.Services.ControllerFactory.CreateController(desc.RescueController);
1865 ControllerMetaDescriptor rescueControllerMeta =
1866 engineContext.Services.ControllerDescriptorProvider.BuildDescriptor(rescueController);
1868 ControllerDescriptor rescueControllerDesc = rescueControllerMeta.ControllerDescriptor;
1870 IControllerContext rescueControllerContext = engineContext.Services.ControllerContextFactory.Create(
1871 rescueControllerDesc.Area, rescueControllerDesc.Name, desc.RescueMethod.Name,
1872 rescueControllerMeta);
1874 rescueControllerContext.CustomActionParameters["exception"] = actionException;
1875 rescueControllerContext.CustomActionParameters["controller"] = this;
1876 rescueControllerContext.CustomActionParameters["controllerContext"] = ControllerContext;
1878 rescueController.Process(engineContext, rescueControllerContext);
1881 #endregion
1883 /// <summary>
1884 /// Pendent
1885 /// </summary>
1886 /// <param name="action">The action.</param>
1887 /// <param name="actions">The actions.</param>
1888 /// <param name="request">The request.</param>
1889 /// <param name="actionArgs">The action args.</param>
1890 /// <returns></returns>
1891 protected virtual MethodInfo SelectMethod(string action, IDictionary actions, IRequest request,
1892 IDictionary<string, object> actionArgs)
1894 return null;
1897 /// <summary>
1898 /// Pendent
1899 /// </summary>
1900 /// <param name="method">The method.</param>
1901 /// <param name="request">The request.</param>
1902 /// <param name="methodArgs">The method args.</param>
1903 protected virtual object InvokeMethod(MethodInfo method, IRequest request,
1904 IDictionary<string, object> methodArgs)
1906 return method.Invoke(this, new object[0]);
1909 /// <summary>
1910 /// Creates the default validator runner.
1911 /// </summary>
1912 /// <param name="validatorRegistry">The validator registry.</param>
1913 /// <returns></returns>
1914 /// <remarks>
1915 /// You can override this method to create a runner
1916 /// with some different configuration
1917 /// </remarks>
1918 protected virtual ValidatorRunner CreateValidatorRunner(IValidatorRegistry validatorRegistry)
1920 if (validatorRegistry == null)
1922 throw new ArgumentNullException("validatorRegistry");
1925 return new ValidatorRunner(validatorRegistry);
1928 /// <summary>
1929 /// Gives a chance to subclasses to format the action name properly
1930 /// </summary>
1931 /// <param name="action">Raw action name</param>
1932 /// <returns>Properly formatted action name</returns>
1933 protected virtual string TransformActionName(string action)
1935 return action;
1938 /// <summary>
1939 /// Registers the exception and notify extensions.
1940 /// </summary>
1941 /// <param name="exception">The exception.</param>
1942 protected void RegisterExceptionAndNotifyExtensions(Exception exception)
1944 engineContext.LastException = exception;
1945 engineContext.Services.ExtensionManager.RaiseActionError(engineContext);