1 // Copyright 2004-2007 Castle Project - http://www.castleproject.org/
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle
.MonoRail
.Framework
18 using System
.Collections
;
20 using Castle
.MonoRail
.Framework
.Helpers
;
21 using Castle
.MonoRail
.Framework
.Internal
;
24 /// Provide easy to use Wizard-like support.
26 /// <seealso cref="IWizardController"/>
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.
33 /// Nevertheless we do require that the programmer
34 /// implements <see cref="IWizardController"/> on the wizard controller.
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
;
48 /// Initializes a new instance of the <see cref="WizardActionProvider"/> class.
50 public WizardActionProvider()
55 /// Implementation of IDynamicActionProvider.
57 /// Grab all steps related to the wizard
58 /// and register them as dynamic actions.
61 /// <param name="controller">Wizard controller (must implement <see cref="IWizardController"/></param>
62 public void IncludeActions(Controller controller
)
64 IRailsEngineContext context
= controller
.Context
;
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();
151 if (!stepExec
.RunStartRequestFilters())
153 context
.UnderlyingContext
.Response
.End();
158 context
.Items
["wizard.step.list"] = stepList
;
160 SetUpWizardHelper(controller
);
161 SetUpWizardHelper(currentStepInstance
);
165 /// Invoked as "start" action
167 /// <param name="controller"></param>
168 public void Execute(Controller controller
)
170 StartWizard(controller
, true);
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
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
))
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
))
221 // TODO: Invoke Whole step here
222 // currentStepInstance.Process(controller.Context,
223 // urlInfo.Area, urlInfo.Controller, innerAction);
224 currentStepExecutor
.ProcessSelectedAction();
228 wizController
.OnAfterStep(wizardName
, currentStep
, currentStepInstance
);
230 currentStepExecutor
.Dispose();
235 /// Represents an empty (no-op) action.
237 /// <param name="controller">The controller.</param>
238 protected void EmptyAction(Controller controller
)
240 controller
.CancelView();
244 /// Determines whether all wizard specific information is on the user session.
246 /// <param name="controller">The controller.</param>
248 /// <c>true</c> if has session data; otherwise, <c>false</c>.
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"));
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.
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();
289 context
.Response
.Redirect(controller
.AreaName
, controller
.Name
, firstStep
);
294 /// Resets the steps by invoking <see cref="WizardStepPage.Reset"/>
295 /// on all steps instances.
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
);
311 private String
ObtainRequestedAction(String action
, out String innerAction
)
315 int index
= action
.IndexOf('-');
319 innerAction
= action
.Substring(index
+ 1);
321 return action
.Substring(0, index
);
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
;