Minor changes to improve testability of helpers
[castle.git] / MonoRail / Castle.MonoRail.Framework / WizardActionProvider.cs
blobf9bde3479aac48af9182eb48daf86d4e55d0855a
1 // Copyright 2004-2007 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;
20 using Castle.MonoRail.Framework.Helpers;
21 using Castle.MonoRail.Framework.Internal;
23 /// <summary>
24 /// Provide easy to use Wizard-like support.
25 /// </summary>
26 /// <seealso cref="IWizardController"/>
27 /// <remarks>
28 /// MonoRail uses the DynamicAction infrastructure to provide
29 /// wizard support so we dont force
30 /// the programmer to inherit from a specific Controller
31 /// which can be quite undesirable in real world projects.
32 /// <para>
33 /// Nevertheless we do require that the programmer
34 /// implements <see cref="IWizardController"/> on the wizard controller.
35 /// </para>
36 /// </remarks>
37 public class WizardActionProvider : IDynamicActionProvider, IDynamicAction
39 private WizardStepPage[] steps;
40 private WizardStepPage currentStepInstance;
41 private IControllerLifecycleExecutor currentStepExecutor;
42 private String rawAction;
43 private String innerAction;
44 private String requestedAction;
45 private UrlInfo urlInfo;
47 /// <summary>
48 /// Initializes a new instance of the <see cref="WizardActionProvider"/> class.
49 /// </summary>
50 public WizardActionProvider()
54 /// <summary>
55 /// Implementation of IDynamicActionProvider.
56 /// <para>
57 /// Grab all steps related to the wizard
58 /// and register them as dynamic actions.
59 /// </para>
60 /// </summary>
61 /// <param name="controller">Wizard controller (must implement <see cref="IWizardController"/></param>
62 public void IncludeActions(Controller controller)
64 IRailsEngineContext context = controller.Context;
66 // Primordial assert
68 IWizardController wizardController = controller as IWizardController;
70 if (wizardController == null)
72 throw new RailsException("The controller {0} must implement the interface " +
73 "IWizardController to be used as a wizard", controller.Name);
76 // Grab all Wizard Steps
78 steps = wizardController.GetSteps(controller.Context);
80 if (steps == null || steps.Length == 0)
82 throw new RailsException("The controller {0} returned no WizardStepPage", controller.Name);
85 IList stepList = new ArrayList();
87 // Include the "start" dynamic action, which resets the wizard state
89 controller.DynamicActions["start"] = this;
91 // Find out the action request (and possible inner action)
92 // Each action will be a step name, or maybe the step name + action (ie Page1-Save)
94 urlInfo = controller.Context.UrlInfo;
96 rawAction = urlInfo.Action;
98 requestedAction = ObtainRequestedAction(rawAction, out innerAction);
100 // If no inner action was found, fallback to 'RenderWizardView'
102 if (innerAction == null || innerAction == String.Empty)
104 innerAction = "RenderWizardView";
107 IControllerLifecycleExecutorFactory execFactory =
108 (IControllerLifecycleExecutorFactory) context.GetService(typeof(IControllerLifecycleExecutorFactory));
110 // Initialize all steps and while we are at it,
111 // discover the current step
113 foreach(WizardStepPage step in steps)
115 String actionName = step.ActionName;
117 if (String.Compare(requestedAction, actionName, true) == 0)
119 currentStepInstance = step;
121 if (innerAction != null)
123 // If there's an inner action, we invoke it as a step too
124 controller.DynamicActions[rawAction] =
125 new DelegateDynamicAction(new ActionDelegate(OnStepActionRequested));
128 context.CurrentController = step;
131 controller.DynamicActions[actionName] =
132 new DelegateDynamicAction(new ActionDelegate(OnStepActionRequested));
134 stepList.Add(actionName);
136 IControllerLifecycleExecutor stepExec = execFactory.CreateExecutor(step, context);
137 stepExec.InitializeController(controller.AreaName, controller.Name, innerAction);
138 step.Initialize(controller);
140 if (currentStepInstance == step)
142 currentStepExecutor = stepExec;
144 if (!stepExec.SelectAction(innerAction, controller.Name))
146 stepExec.PerformErrorHandling();
148 return;
151 if (!stepExec.RunStartRequestFilters())
153 context.UnderlyingContext.Response.End();
158 context.Items["wizard.step.list"] = stepList;
160 SetUpWizardHelper(controller);
161 SetUpWizardHelper(currentStepInstance);
164 /// <summary>
165 /// Invoked as "start" action
166 /// </summary>
167 /// <param name="controller"></param>
168 public void Execute(Controller controller)
170 StartWizard(controller, true);
173 /// <summary>
174 /// Invoked when a step is accessed on the url,
175 /// i.e. http://host/mywizard/firststep.rails and
176 /// when an inner action is invoked like http://host/mywizard/firststep-save.rails
177 /// </summary>
178 /// <param name="controller"></param>
179 private void OnStepActionRequested(Controller controller)
181 if (currentStepInstance != null && !HasRequiredSessionData(controller))
183 StartWizard(controller, false);
186 controller.CancelView();
188 IRailsEngineContext context = controller.Context;
190 IWizardController wizController = (IWizardController) controller;
192 String wizardName = WizardUtils.ConstructWizardNamespace(controller);
194 String currentStep = (String) context.Session[wizardName + "currentstep"];
196 // The step will inherit the controller property bag,
197 // this way filters can pass values to the step property without having to know it
198 currentStepInstance.PropertyBag = controller.PropertyBag;
200 // If OnBeforeStep returns false we stop
201 if (!wizController.OnBeforeStep(wizardName, currentStep, currentStepInstance))
203 return;
206 // Initialize step data so instance members can be used
207 // executor.InitializeController(urlInfo.Area, urlInfo.Controller, innerAction);
209 // Record the step we're working with
210 WizardUtils.RegisterCurrentStepInfo(controller, currentStepInstance.ActionName);
212 // The step cannot be accessed in the current state of matters
213 if (!currentStepInstance.IsPreConditionSatisfied(controller.Context))
215 return;
218 // Dispatch process
221 // TODO: Invoke Whole step here
222 // currentStepInstance.Process(controller.Context,
223 // urlInfo.Area, urlInfo.Controller, innerAction);
224 currentStepExecutor.ProcessSelectedAction();
226 finally
228 wizController.OnAfterStep(wizardName, currentStep, currentStepInstance);
230 currentStepExecutor.Dispose();
234 /// <summary>
235 /// Represents an empty (no-op) action.
236 /// </summary>
237 /// <param name="controller">The controller.</param>
238 protected void EmptyAction(Controller controller)
240 controller.CancelView();
243 /// <summary>
244 /// Determines whether all wizard specific information is on the user session.
245 /// </summary>
246 /// <param name="controller">The controller.</param>
247 /// <returns>
248 /// <c>true</c> if has session data; otherwise, <c>false</c>.
249 /// </returns>
250 protected bool HasRequiredSessionData(Controller controller)
252 String wizardName = WizardUtils.ConstructWizardNamespace(controller);
254 IRailsEngineContext context = controller.Context;
256 return (context.Session.Contains(wizardName + "currentstepindex") &&
257 context.Session.Contains(wizardName + "currentstep"));
260 /// <summary>
261 /// Starts the wizard by adding the required information to the
262 /// session and invoking <see cref="IWizardController.OnWizardStart"/>
263 /// and detecting the first step.
264 /// </summary>
265 /// <param name="controller">The controller.</param>
266 /// <param name="redirect">if set to <c>true</c>, a redirect
267 /// will be issued to the first step.</param>
268 protected void StartWizard(Controller controller, bool redirect)
270 ResetSteps(controller);
272 IWizardController wizardController = controller as IWizardController;
274 IRailsEngineContext context = controller.Context;
276 IList stepList = (IList) context.Items["wizard.step.list"];
278 String firstStep = (String) stepList[0];
280 String wizardName = WizardUtils.ConstructWizardNamespace(controller);
282 context.Session[wizardName + "currentstepindex"] = 0;
283 context.Session[wizardName + "currentstep"] = firstStep;
285 wizardController.OnWizardStart();
287 if (redirect)
289 context.Response.Redirect(controller.AreaName, controller.Name, firstStep);
293 /// <summary>
294 /// Resets the steps by invoking <see cref="WizardStepPage.Reset"/>
295 /// on all steps instances.
296 /// </summary>
297 /// <param name="controller">The controller.</param>
298 protected void ResetSteps(Controller controller)
300 IWizardController wizardController = controller as IWizardController;
302 WizardStepPage[] steps = wizardController.GetSteps(controller.Context);
304 foreach(WizardStepPage step in steps)
306 step.InitializeFieldsFromServiceProvider(controller.Context);
307 step.Reset();
311 private String ObtainRequestedAction(String action, out String innerAction)
313 innerAction = null;
315 int index = action.IndexOf('-');
317 if (index != -1)
319 innerAction = action.Substring(index + 1);
321 return action.Substring(0, index);
324 return action;
327 private void SetUpWizardHelper(Controller controller)
329 if (controller == null) return;
331 WizardHelper helper = new WizardHelper();
333 helper.SetController(controller);
335 controller.Helpers["wizardhelper"] = helper;