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 /// <returns></returns>
179 protected override MethodInfo
SelectMethod(string action
, IDictionary actions
, IRequest request
, IDictionary
<string, object> actionArgs
)
181 if (action
== "RenderWizardView")
183 return typeof(WizardStepPage
).GetMethod("RenderWizardView", BindingFlags
.Instance
| BindingFlags
.NonPublic
);
187 return base.SelectMethod(action
, actions
, request
, actionArgs
);
194 /// Override takes care of selecting the wizard parent layout as default
195 /// layout if no layout is attached to the step
197 protected override void ResolveLayout()
199 base.ResolveLayout();
201 if (LayoutName
== null)
203 LayoutNames
= wizardcontrollerContext
.LayoutNames
;
207 #region DoNavigate and Redirects
210 /// Navigates within the wizard steps using optionally a form parameter
211 /// to dictate to where it should go.
214 /// By default this will invoke <see cref="RedirectToNextStep(IDictionary)"/>
215 /// however you can send a field form <c>navigate.to</c> to customize this.
216 /// The possible values for <c>navigate.to</c> are:
217 /// <list type="bullet">
218 /// <item><term>previous</term>
219 /// <description>Invokes <see cref="RedirectToPreviousStep()"/></description></item>
220 /// <item><term>first</term>
221 /// <description>Invokes <see cref="RedirectToFirstStep()"/></description></item>
222 /// <item><term>step name</term>
223 /// <description>A custom step name to navigate</description></item>
226 protected void DoNavigate()
228 DoNavigate((IDictionary
) null);
232 /// Navigates within the wizard steps using optionally a form parameter
233 /// to dictate to where it should go.
236 /// By default this will invoke <see cref="RedirectToNextStep(IDictionary)"/>
237 /// however you can send a field form <c>navigate.to</c> to customize this.
238 /// The possible values for <c>navigate.to</c> are:
239 /// <list type="bullet">
240 /// <item><term>previous</term>
241 /// <description>Invokes <see cref="RedirectToPreviousStep()"/></description></item>
242 /// <item><term>first</term>
243 /// <description>Invokes <see cref="RedirectToFirstStep()"/></description></item>
244 /// <item><term>step name</term>
245 /// <description>A custom step name to navigate</description></item>
248 /// <param name="queryStringParameters">Query string parameters to be on the URL</param>
249 protected void DoNavigate(params String
[] queryStringParameters
)
251 DoNavigate(DictHelper
.Create(queryStringParameters
));
255 /// Navigates within the wizard steps using optionally a form parameter
256 /// to dictate to where it should go.
259 /// By default this will invoke <see cref="RedirectToNextStep(IDictionary)"/>
260 /// however you can send a field form <c>navigate.to</c> to customize this.
261 /// The possible values for <c>navigate.to</c> are:
262 /// <list type="bullet">
263 /// <item><term>previous</term>
264 /// <description>Invokes <see cref="RedirectToPreviousStep()"/></description></item>
265 /// <item><term>first</term>
266 /// <description>Invokes <see cref="RedirectToFirstStep()"/></description></item>
267 /// <item><term>step name</term>
268 /// <description>A custom step name to navigate</description></item>
271 /// <param name="queryStringParameters">Query string parameters to be on the URL</param>
272 protected void DoNavigate(IDictionary queryStringParameters
)
274 string uriPrefix
= "uri:";
276 String navigateTo
= Params
["navigate.to"];
278 if (navigateTo
== "previous")
280 RedirectToPreviousStep(queryStringParameters
);
282 else if (navigateTo
== null || navigateTo
== String
.Empty
|| navigateTo
== "next")
284 RedirectToNextStep(queryStringParameters
);
286 else if (navigateTo
.StartsWith(uriPrefix
))
288 RedirectToUrl(navigateTo
.Substring(uriPrefix
.Length
), queryStringParameters
);
290 else if (navigateTo
== "first")
292 RedirectToFirstStep(queryStringParameters
);
296 RedirectToStep(navigateTo
, queryStringParameters
);
301 /// Sends a redirect to the next wizard step (if it exists)
303 /// <exception cref="MonoRailException">if no further step exists</exception>
304 protected void RedirectToNextStep()
306 RedirectToNextStep((IDictionary
) null);
310 /// Sends a redirect to the next wizard step (if it exists)
312 /// <exception cref="MonoRailException">if no further step exists</exception>
313 protected void RedirectToNextStep(params String
[] queryStringParameters
)
315 RedirectToNextStep(DictHelper
.Create(queryStringParameters
));
319 /// Sends a redirect to the next wizard step (if it exists)
321 /// <exception cref="MonoRailException">if no further step exists</exception>
322 protected void RedirectToNextStep(IDictionary queryStringParameters
)
324 String wizardName
= WizardUtils
.ConstructWizardNamespace(ControllerContext
);
326 int currentIndex
= (int) Context
.Session
[wizardName
+ "currentstepindex"];
328 IList stepList
= (IList
) Context
.Items
["wizard.step.list"];
330 if ((currentIndex
+ 1) < stepList
.Count
)
332 int nextStepIndex
= currentIndex
+ 1;
334 String nextStep
= (String
) stepList
[nextStepIndex
];
336 WizardUtils
.RegisterCurrentStepInfo(Context
, wizardParentController
, ControllerContext
, nextStepIndex
, nextStep
);
338 InternalRedirectToStep(Context
, nextStepIndex
, nextStep
, queryStringParameters
);
342 throw new MonoRailException("There is no next step available");
347 /// Sends a redirect to the previous wizard step
349 /// <exception cref="MonoRailException">
350 /// if no previous step exists (ie. already in the first one)</exception>
351 protected void RedirectToPreviousStep()
353 RedirectToPreviousStep((IDictionary
) null);
357 /// Sends a redirect to the previous wizard step
359 /// <exception cref="MonoRailException">
360 /// if no previous step exists (ie. already in the first one)</exception>
361 protected void RedirectToPreviousStep(params String
[] queryStringParameters
)
363 RedirectToPreviousStep(DictHelper
.Create(queryStringParameters
));
367 /// Sends a redirect to the previous wizard step
369 /// <exception cref="MonoRailException">
370 /// if no previous step exists (ie. already in the first one)</exception>
371 protected void RedirectToPreviousStep(IDictionary queryStringParameters
)
373 String wizardName
= WizardUtils
.ConstructWizardNamespace(wizardcontrollerContext
);
375 int currentIndex
= (int) Context
.Session
[wizardName
+ "currentstepindex"];
377 IList stepList
= (IList
) Context
.Items
["wizard.step.list"];
379 if ((currentIndex
- 1) >= 0)
381 int prevStepIndex
= currentIndex
- 1;
383 String prevStep
= (String
) stepList
[prevStepIndex
];
385 InternalRedirectToStep(Context
, prevStepIndex
, prevStep
, queryStringParameters
);
389 throw new MonoRailException("There is no previous step available");
394 /// Sends a redirect to the first wizard step
396 protected void RedirectToFirstStep()
398 RedirectToFirstStep((IDictionary
) null);
402 /// Sends a redirect to the first wizard step
404 protected void RedirectToFirstStep(params String
[] queryStringParameters
)
406 RedirectToFirstStep(DictHelper
.Create(queryStringParameters
));
410 /// Sends a redirect to the first wizard step
412 protected void RedirectToFirstStep(IDictionary queryStringParameters
)
414 IList stepList
= (IList
) Context
.Items
["wizard.step.list"];
416 String firstStep
= (String
) stepList
[0];
418 InternalRedirectToStep(Context
, 0, firstStep
, queryStringParameters
);
422 /// Sends a redirect to a custom step (that must exists)
424 protected bool RedirectToStep(String stepName
)
426 return RedirectToStep(stepName
, (IDictionary
) null);
430 /// Sends a redirect to a custom step (that must exists)
432 protected bool RedirectToStep(String stepName
, params String
[] queryStringParameters
)
434 return RedirectToStep(stepName
, DictHelper
.Create(queryStringParameters
));
438 /// Sends a redirect to a custom step (that must exists)
440 protected bool RedirectToStep(String stepName
, IDictionary queryStringParameters
)
442 IList stepList
= (IList
) Context
.Items
["wizard.step.list"];
444 for(int index
= 0; index
< stepList
.Count
; index
++)
446 String curStep
= (String
) stepList
[index
];
448 if (curStep
== stepName
)
450 InternalRedirectToStep(Context
, index
, stepName
, queryStringParameters
);
459 /// For a wizard step, an internal action will always be named
460 /// with the controller name as a prefix , plus an hifen and finally
461 /// the action name. This implementation does exactly that.
463 /// <param name="action">Raw action name</param>
464 /// <returns>Properly formatted action name</returns>
465 protected override String
TransformActionName(String action
)
467 return base.TransformActionName(ActionName
+ "-" + action
);
470 private void InternalRedirectToStep(IEngineContext engineContext
, int stepIndex
, String step
,
471 IDictionary queryStringParameters
)
473 WizardUtils
.RegisterCurrentStepInfo(engineContext
, wizardParentController
, wizardcontrollerContext
, stepIndex
, step
);
475 // Does this support areas?
477 if (queryStringParameters
!= null && queryStringParameters
.Count
!= 0)
479 Redirect(WizardControllerContext
.AreaName
, wizardcontrollerContext
.Name
, step
, queryStringParameters
);
481 else if (Context
.Request
.QueryString
.HasKeys())
483 // We need to preserve any attribute from the QueryString
484 // for example in case the url has an Id
486 Redirect(WizardControllerContext
.AreaName
, wizardcontrollerContext
.Name
, step
, Query
);
490 Redirect(WizardControllerContext
.AreaName
, wizardcontrollerContext
.Name
, step
);