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 System
.Reflection
;
21 using Castle
.Components
.Binder
;
22 using Castle
.MonoRail
.Framework
.Helpers
;
23 using Castle
.MonoRail
.Framework
.Internal
;
27 /// Represents a wizard step. In essence it is a controller, but with some subtle differences.
28 /// See the remarks for more information.
31 /// <seealso xref="WizardActionProvider"/>
32 /// <seealso xref="IWizardController"/>
35 /// Implementors can optionally override <see cref="WizardStepPage.ActionName"/>
36 /// to customize the accessible action name and
37 /// <see cref="WizardStepPage.RenderWizardView"/> in order to define which view
38 /// should be used (defaults to the step name)
41 /// Please note that an step might have actions as well, but it follows a different
42 /// convention to be accessed. You must use the wizard controller name, slash, the
43 /// step name, hifen, the action name. For example <c>/MyWizard/AddressInformation-GetCountries.rails</c>
44 /// Which would access the following action
48 /// public class AddressInformation : WizardStepPage
50 /// public void GetCountries()
57 /// Note that the RedirectToAction will always send to an internal action, so you should
58 /// omit the controller name for that.
62 /// You can use a family of redirect methods to go back and forward on the wizard's
66 public abstract class WizardStepPage
: SmartDispatcherController
, IWizardStepPage
70 private IWizardController wizardParentController
;
71 private IControllerContext wizardcontrollerContext
;
78 /// Initializes a new instance of the <see cref="WizardStepPage"/> class.
80 public WizardStepPage()
85 /// Initializes a new instance of the <see cref="WizardStepPage"/> class.
87 /// <param name="binder">The binder.</param>
88 public WizardStepPage(IDataBinder binder
) : base(binder
)
94 #region Useful Properties
97 /// Gets the wizard controller.
99 /// <value>The wizard controller.</value>
100 public IWizardController WizardController
102 get { return wizardParentController; }
103 set { wizardParentController = value; }
107 /// Gets the controller context.
109 /// <value>The controller context.</value>
110 public IControllerContext WizardControllerContext
112 get { return wizardcontrollerContext; }
113 set { wizardcontrollerContext = value; }
118 #region Core Lifecycle methods
121 /// Invoked when the wizard is being access from the start
122 /// action. Implementors should perform session clean up (if
123 /// they actually use the session) to avoid stale data on forms.
125 public virtual void Reset()
130 /// Returns the action name that will be used
131 /// to represent this step.
133 public virtual String ActionName
137 Type thisType
= GetType();
139 // Hack fix for "dynamic proxied" controllers
140 if (thisType
.Assembly
.FullName
.StartsWith("DynamicAssemblyProxyGen") ||
141 thisType
.Assembly
.FullName
.StartsWith("DynamicProxyGenAssembly2"))
143 return thisType
.BaseType
.Name
;
146 return GetType().Name
;
151 /// Used to decide on which view to render.
153 protected internal virtual void RenderWizardView()
155 RenderView(ActionName
);
159 /// Allow the step to assert some condition
160 /// before being accessed. Returning <c>false</c>
161 /// prevents the step from being processed but
162 /// before doing that you must send a redirect.
164 /// <returns></returns>
165 public virtual bool IsPreConditionSatisfied(IEngineContext context
)
171 /// Uses a simple heuristic to select the best method -- especially in the
172 /// case of method overloads.
174 /// <param name="action">The action name</param>
175 /// <param name="actions">The avaliable actions</param>
176 /// <param name="request">The request instance</param>
177 /// <param name="actionArgs">The custom arguments for the action</param>
178 /// <param name="actionType">Type of the action.</param>
179 /// <returns></returns>
180 protected override MethodInfo
SelectMethod(string action
, IDictionary actions
, IRequest request
, IDictionary
<string, object> actionArgs
, ActionType actionType
)
182 if (action
== "RenderWizardView")
184 return typeof(WizardStepPage
).GetMethod("RenderWizardView", BindingFlags
.Instance
| BindingFlags
.NonPublic
);
188 return base.SelectMethod(action
, actions
, request
, actionArgs
, actionType
);
195 /// Override takes care of selecting the wizard parent layout as default
196 /// layout if no layout is attached to the step
198 protected override void ResolveLayout()
200 base.ResolveLayout();
202 if (LayoutName
== null)
204 LayoutNames
= wizardcontrollerContext
.LayoutNames
;
208 #region DoNavigate and Redirects
211 /// Navigates within the wizard steps using optionally a form parameter
212 /// to dictate to where it should go.
215 /// By default this will invoke <see cref="RedirectToNextStep(IDictionary)"/>
216 /// however you can send a field form <c>navigate.to</c> to customize this.
217 /// The possible values for <c>navigate.to</c> are:
218 /// <list type="bullet">
219 /// <item><term>previous</term>
220 /// <description>Invokes <see cref="RedirectToPreviousStep()"/></description></item>
221 /// <item><term>first</term>
222 /// <description>Invokes <see cref="RedirectToFirstStep()"/></description></item>
223 /// <item><term>step name</term>
224 /// <description>A custom step name to navigate</description></item>
227 protected virtual void DoNavigate()
229 DoNavigate((IDictionary
) null);
233 /// Navigates within the wizard steps using optionally a form parameter
234 /// to dictate to where it should go.
237 /// By default this will invoke <see cref="RedirectToNextStep(IDictionary)"/>
238 /// however you can send a field form <c>navigate.to</c> to customize this.
239 /// The possible values for <c>navigate.to</c> are:
240 /// <list type="bullet">
241 /// <item><term>previous</term>
242 /// <description>Invokes <see cref="RedirectToPreviousStep()"/></description></item>
243 /// <item><term>first</term>
244 /// <description>Invokes <see cref="RedirectToFirstStep()"/></description></item>
245 /// <item><term>step name</term>
246 /// <description>A custom step name to navigate</description></item>
249 /// <param name="queryStringParameters">Query string parameters to be on the URL</param>
250 protected virtual void DoNavigate(params String
[] queryStringParameters
)
252 DoNavigate(DictHelper
.Create(queryStringParameters
));
256 /// Navigates within the wizard steps using optionally a form parameter
257 /// to dictate to where it should go.
260 /// By default this will invoke <see cref="RedirectToNextStep(IDictionary)"/>
261 /// however you can send a field form <c>navigate.to</c> to customize this.
262 /// The possible values for <c>navigate.to</c> are:
263 /// <list type="bullet">
264 /// <item><term>previous</term>
265 /// <description>Invokes <see cref="RedirectToPreviousStep()"/></description></item>
266 /// <item><term>first</term>
267 /// <description>Invokes <see cref="RedirectToFirstStep()"/></description></item>
268 /// <item><term>step name</term>
269 /// <description>A custom step name to navigate</description></item>
272 /// <param name="queryStringParameters">Query string parameters to be on the URL</param>
273 protected virtual void DoNavigate(IDictionary queryStringParameters
)
275 string uriPrefix
= "uri:";
277 String navigateTo
= Params
["navigate.to"];
279 if (navigateTo
== "previous")
281 RedirectToPreviousStep(queryStringParameters
);
283 else if (navigateTo
== null || navigateTo
== String
.Empty
|| navigateTo
== "next")
285 RedirectToNextStep(queryStringParameters
);
287 else if (navigateTo
.StartsWith(uriPrefix
))
289 RedirectToUrl(navigateTo
.Substring(uriPrefix
.Length
), queryStringParameters
);
291 else if (navigateTo
== "first")
293 RedirectToFirstStep(queryStringParameters
);
297 RedirectToStep(navigateTo
, queryStringParameters
);
302 /// Sends a redirect to the next wizard step (if it exists)
304 /// <exception cref="MonoRailException">if no further step exists</exception>
305 protected virtual void RedirectToNextStep()
307 RedirectToNextStep((IDictionary
) null);
311 /// Sends a redirect to the next wizard step (if it exists)
313 /// <exception cref="MonoRailException">if no further step exists</exception>
314 protected virtual void RedirectToNextStep(params String
[] queryStringParameters
)
316 RedirectToNextStep(DictHelper
.Create(queryStringParameters
));
320 /// Sends a redirect to the next wizard step (if it exists)
322 /// <exception cref="MonoRailException">if no further step exists</exception>
323 protected virtual void RedirectToNextStep(IDictionary queryStringParameters
)
325 String wizardName
= WizardUtils
.ConstructWizardNamespace(ControllerContext
);
327 int currentIndex
= (int) Context
.Session
[wizardName
+ "currentstepindex"];
329 IList stepList
= (IList
) Context
.Items
["wizard.step.list"];
331 if ((currentIndex
+ 1) < stepList
.Count
)
333 int nextStepIndex
= currentIndex
+ 1;
335 String nextStep
= (String
) stepList
[nextStepIndex
];
337 WizardUtils
.RegisterCurrentStepInfo(Context
, wizardParentController
, ControllerContext
, nextStepIndex
, nextStep
);
339 InternalRedirectToStep(Context
, nextStepIndex
, nextStep
, queryStringParameters
);
343 throw new MonoRailException("There is no next step available");
348 /// Sends a redirect to the previous wizard step
350 /// <exception cref="MonoRailException">
351 /// if no previous step exists (ie. already in the first one)</exception>
352 protected virtual void RedirectToPreviousStep()
354 RedirectToPreviousStep((IDictionary
) null);
358 /// Sends a redirect to the previous wizard step
360 /// <exception cref="MonoRailException">
361 /// if no previous step exists (ie. already in the first one)</exception>
362 protected virtual void RedirectToPreviousStep(params String
[] queryStringParameters
)
364 RedirectToPreviousStep(DictHelper
.Create(queryStringParameters
));
368 /// Sends a redirect to the previous wizard step
370 /// <exception cref="MonoRailException">
371 /// if no previous step exists (ie. already in the first one)</exception>
372 protected virtual void RedirectToPreviousStep(IDictionary queryStringParameters
)
374 String wizardName
= WizardUtils
.ConstructWizardNamespace(wizardcontrollerContext
);
376 int currentIndex
= (int) Context
.Session
[wizardName
+ "currentstepindex"];
378 IList stepList
= (IList
) Context
.Items
["wizard.step.list"];
380 if ((currentIndex
- 1) >= 0)
382 int prevStepIndex
= currentIndex
- 1;
384 String prevStep
= (String
) stepList
[prevStepIndex
];
386 InternalRedirectToStep(Context
, prevStepIndex
, prevStep
, queryStringParameters
);
390 throw new MonoRailException("There is no previous step available");
395 /// Sends a redirect to the first wizard step
397 protected virtual void RedirectToFirstStep()
399 RedirectToFirstStep((IDictionary
) null);
403 /// Sends a redirect to the first wizard step
405 protected virtual void RedirectToFirstStep(params String
[] queryStringParameters
)
407 RedirectToFirstStep(DictHelper
.Create(queryStringParameters
));
411 /// Sends a redirect to the first wizard step
413 protected virtual void RedirectToFirstStep(IDictionary queryStringParameters
)
415 IList stepList
= (IList
) Context
.Items
["wizard.step.list"];
417 String firstStep
= (String
) stepList
[0];
419 InternalRedirectToStep(Context
, 0, firstStep
, queryStringParameters
);
423 /// Sends a redirect to a custom step (that must exists)
425 protected virtual bool RedirectToStep(String stepName
)
427 return RedirectToStep(stepName
, (IDictionary
) null);
431 /// Sends a redirect to a custom step (that must exists)
433 protected virtual bool RedirectToStep(String stepName
, params String
[] queryStringParameters
)
435 return RedirectToStep(stepName
, DictHelper
.Create(queryStringParameters
));
439 /// Sends a redirect to a custom step (that must exists)
441 protected virtual bool RedirectToStep(String stepName
, IDictionary queryStringParameters
)
443 IList stepList
= (IList
) Context
.Items
["wizard.step.list"];
445 for(int index
= 0; index
< stepList
.Count
; index
++)
447 String curStep
= (String
) stepList
[index
];
449 if (curStep
== stepName
)
451 InternalRedirectToStep(Context
, index
, stepName
, queryStringParameters
);
460 /// For a wizard step, an internal action will always be named
461 /// with the controller name as a prefix , plus an hifen and finally
462 /// the action name. This implementation does exactly that.
464 /// <param name="action">Raw action name</param>
465 /// <returns>Properly formatted action name</returns>
466 protected override String
TransformActionName(String action
)
468 return base.TransformActionName(ActionName
+ "-" + action
);
471 private void InternalRedirectToStep(IEngineContext engineContext
, int stepIndex
, String step
,
472 IDictionary queryStringParameters
)
474 WizardUtils
.RegisterCurrentStepInfo(engineContext
, wizardParentController
, wizardcontrollerContext
, stepIndex
, step
);
476 // Does this support areas?
478 if (queryStringParameters
!= null && queryStringParameters
.Count
!= 0)
480 Redirect(WizardControllerContext
.AreaName
, wizardcontrollerContext
.Name
, step
, queryStringParameters
);
482 else if (Context
.Request
.QueryString
.HasKeys())
484 // We need to preserve any attribute from the QueryString
485 // for example in case the url has an Id
487 Redirect(WizardControllerContext
.AreaName
, wizardcontrollerContext
.Name
, step
, Query
);
491 Redirect(WizardControllerContext
.AreaName
, wizardcontrollerContext
.Name
, step
);