1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle
.MonoRail
.Framework
18 using System
.Collections
;
19 using System
.Collections
.Generic
;
20 using Castle
.MonoRail
.Framework
.Helpers
;
21 using Castle
.MonoRail
.Framework
.Internal
;
22 using Castle
.MonoRail
.Framework
.Descriptors
;
25 /// Provide easy to use Wizard-like support.
27 /// <seealso cref="IWizardController"/>
29 /// MonoRail uses the DynamicAction infrastructure to provide
30 /// wizard support so we dont force
31 /// the programmer to inherit from a specific Controller
32 /// which can be quite undesirable in real world projects.
34 /// Nevertheless we do require that the programmer
35 /// implements <see cref="IWizardController"/> on the wizard controller.
38 public class WizardActionProvider
: IDynamicActionProvider
, IDynamicAction
40 private IWizardStepPage
[] steps
;
41 private IWizardStepPage currentStepInstance
;
42 private String rawAction
;
43 private String innerAction
;
44 private String requestedAction
;
45 private UrlInfo urlInfo
;
48 /// Implementation of IDynamicActionProvider.
50 /// Grab all steps related to the wizard
51 /// and register them as dynamic actions.
54 /// <param name="engineContext">The engine context.</param>
55 /// <param name="controller">Wizard controller (must implement <see cref="IWizardController"/></param>
56 /// <param name="controllerContext">The controller context.</param>
57 public void IncludeActions(IEngineContext engineContext
, IController controller
, IControllerContext controllerContext
)
61 IWizardController wizardController
= controller
as IWizardController
;
63 if (wizardController
== null)
65 throw new MonoRailException("The controller {0} must implement the interface " +
66 "IWizardController to be used as a wizard", controllerContext
.Name
);
69 // Grab all Wizard Steps
71 steps
= wizardController
.GetSteps(engineContext
);
73 if (steps
== null || steps
.Length
== 0)
75 throw new MonoRailException("The controller {0} returned no WizardStepPage", controllerContext
.Name
);
78 List
<string> stepList
= new List
<string>();
80 // Include the "start" dynamic action, which resets the wizard state
82 controllerContext
.DynamicActions
["start"] = this;
84 // Find out the action request (and possible inner action)
85 // Each action will be a step name, or maybe the step name + action (ie Page1-Save)
87 urlInfo
= engineContext
.UrlInfo
;
89 rawAction
= urlInfo
.Action
;
91 requestedAction
= ObtainRequestedAction(rawAction
, out innerAction
);
93 // If no inner action was found, fallback to 'RenderWizardView'
95 if (string.IsNullOrEmpty(innerAction
))
97 innerAction
= "RenderWizardView";
100 engineContext
.Items
["wizard.step.list"] = stepList
;
102 SetUpWizardHelper(engineContext
, controller
, controllerContext
);
104 // Initialize all steps and while we are at it,
105 // discover the current step
107 foreach(IWizardStepPage step
in steps
)
109 string actionName
= step
.ActionName
;
111 step
.WizardController
= wizardController
;
112 step
.WizardControllerContext
= controllerContext
;
114 if (string.Compare(requestedAction
, actionName
, true) == 0)
116 currentStepInstance
= step
;
118 if (innerAction
!= null)
120 // If there's an inner action, we invoke it as a step too
121 controllerContext
.DynamicActions
[rawAction
] = new DelegateDynamicAction(OnStepActionRequested
);
124 engineContext
.CurrentController
= step
;
128 controllerContext
.DynamicActions
[actionName
] = new DelegateDynamicAction(OnStepActionRequested
);
131 stepList
.Add(actionName
);
134 SetUpWizardHelper(engineContext
, controller
, controllerContext
);
138 /// Invoked as "start" action
140 /// <returns></returns>
141 public object Execute(IEngineContext engineContext
, IController controller
, IControllerContext controllerContext
)
143 StartWizard(engineContext
, controller
, controllerContext
, true);
148 /// Invoked when a step is accessed on the url,
149 /// i.e. http://host/mywizard/firststep.rails and
150 /// when an inner action is invoked like http://host/mywizard/firststep-save.rails
152 /// <param name="engineContext">The engine context.</param>
153 /// <param name="controller">The controller.</param>
154 /// <param name="controllerContext">The controller context.</param>
155 private object OnStepActionRequested(IEngineContext engineContext
, IController controller
, IControllerContext controllerContext
)
157 if (currentStepInstance
!= null && !HasRequiredSessionData(engineContext
, controller
, controllerContext
))
159 StartWizard(engineContext
, controller
, controllerContext
, false);
162 controllerContext
.SelectedViewName
= null;
164 IWizardController wizController
= (IWizardController
) controller
;
166 String wizardName
= WizardUtils
.ConstructWizardNamespace(controllerContext
);
167 String currentStep
= (String
) engineContext
.Session
[wizardName
+ "currentstep"];
169 // If OnBeforeStep returns false we stop
170 if (!wizController
.OnBeforeStep(wizardName
, currentStep
, currentStepInstance
))
175 ControllerMetaDescriptor stepMetaDescriptor
=
176 engineContext
.Services
.ControllerDescriptorProvider
.BuildDescriptor(currentStepInstance
);
178 // Record the step we're working with
179 WizardUtils
.RegisterCurrentStepInfo(engineContext
, controller
, controllerContext
, currentStepInstance
.ActionName
);
183 IControllerContext stepContext
=
184 engineContext
.Services
.ControllerContextFactory
.Create(
185 controllerContext
.AreaName
, controllerContext
.Name
, innerAction
,
186 stepMetaDescriptor
, controllerContext
.RouteMatch
);
187 stepContext
.PropertyBag
= controllerContext
.PropertyBag
;
189 SetUpWizardHelper(engineContext
, currentStepInstance
, stepContext
);
191 // IsPreConditionSatisfied might need the controller's context
192 if (currentStepInstance
is Controller
)
194 ((Controller
)currentStepInstance
).Contextualize(engineContext
, stepContext
);
197 // The step cannot be accessed in the current state of matters
198 if (!currentStepInstance
.IsPreConditionSatisfied(engineContext
))
203 currentStepInstance
.Process(engineContext
, stepContext
);
209 wizController
.OnAfterStep(wizardName
, currentStep
, currentStepInstance
);
214 /// Represents an empty (no-op) action.
216 /// <param name="controller">The controller.</param>
217 protected void EmptyAction(Controller controller
)
219 controller
.CancelView();
223 /// Determines whether all wizard specific information is on the user session.
225 /// <param name="engineContext">The engine context.</param>
226 /// <param name="controller">The controller.</param>
227 /// <param name="controllerContext">The controller context.</param>
229 /// <c>true</c> if has session data; otherwise, <c>false</c>.
231 protected bool HasRequiredSessionData(IEngineContext engineContext
, IController controller
, IControllerContext controllerContext
)
233 String wizardName
= WizardUtils
.ConstructWizardNamespace(controllerContext
);
235 return (engineContext
.Session
.Contains(wizardName
+ "currentstepindex") &&
236 engineContext
.Session
.Contains(wizardName
+ "currentstep"));
240 /// Starts the wizard by adding the required information to the
241 /// session and invoking <see cref="IWizardController.OnWizardStart"/>
242 /// and detecting the first step.
244 /// <param name="engineContext">The engine context.</param>
245 /// <param name="controller">The controller.</param>
246 /// <param name="controllerContext">The controller context.</param>
247 /// <param name="redirect">if set to <c>true</c>, a redirect
248 /// will be issued to the first step.</param>
249 protected void StartWizard(IEngineContext engineContext
, IController controller
, IControllerContext controllerContext
, bool redirect
)
251 ResetSteps(engineContext
, controller
);
253 IWizardController wizardController
= (IWizardController
) controller
;
255 IList stepList
= (IList
) engineContext
.Items
["wizard.step.list"];
257 String firstStep
= (String
) stepList
[0];
259 String wizardName
= WizardUtils
.ConstructWizardNamespace(controllerContext
);
261 engineContext
.Session
[wizardName
+ "currentstepindex"] = 0;
262 engineContext
.Session
[wizardName
+ "currentstep"] = firstStep
;
264 wizardController
.OnWizardStart();
268 engineContext
.Response
.Redirect(controllerContext
.AreaName
, controllerContext
.Name
, firstStep
);
273 /// Resets the steps by invoking <see cref="IWizardStepPage.Reset"/>
274 /// on all steps instances.
276 /// <param name="engineContext">The engine context.</param>
277 /// <param name="controller">The controller.</param>
278 protected void ResetSteps(IEngineContext engineContext
, IController controller
)
280 IWizardController wizardController
= (IWizardController
) controller
;
282 IWizardStepPage
[] steps
= wizardController
.GetSteps(engineContext
);
284 foreach(IWizardStepPage step
in steps
)
290 private String
ObtainRequestedAction(String action
, out String innerAction
)
294 int index
= action
.IndexOf('-');
298 innerAction
= action
.Substring(index
+ 1);
300 return action
.Substring(0, index
);
306 private void SetUpWizardHelper(IEngineContext engineContext
, IController controller
, IControllerContext controllerContext
)
308 if (controller
== null) return;
310 WizardHelper helper
= new WizardHelper();
312 helper
.SetContext(engineContext
);
313 helper
.SetController(controller
, controllerContext
);
315 controllerContext
.Helpers
.Add(helper
);