More working tests.
[castle.git] / MonoRail / Castle.MonoRail.Framework / WizardStepPage.cs
blob637201b93f2cd9ec319aeb2df86279350d7cbd86
1 // Copyright 2004-2008 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;
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;
24 using Services;
26 /// <summary>
27 /// Represents a wizard step. In essence it is a controller, but with some subtle differences.
28 /// See the remarks for more information.
29 /// </summary>
30 ///
31 /// <seealso xref="WizardActionProvider"/>
32 /// <seealso xref="IWizardController"/>
33 ///
34 /// <remarks>
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)
39 ///
40 /// <para>
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
45 /// </para>
46 ///
47 /// <code>
48 /// public class AddressInformation : WizardStepPage
49 /// {
50 /// public void GetCountries()
51 /// {
52 /// ...
53 /// }
54 /// }
55 /// </code>
56 /// <para>
57 /// Note that the RedirectToAction will always send to an internal action, so you should
58 /// omit the controller name for that.
59 /// </para>
60 ///
61 /// <para>
62 /// You can use a family of redirect methods to go back and forward on the wizard's
63 /// steps.
64 /// </para>
65 /// </remarks>
66 public abstract class WizardStepPage : SmartDispatcherController, IWizardStepPage
68 #region Fields
70 private IWizardController wizardParentController;
71 private IControllerContext wizardcontrollerContext;
73 #endregion
75 #region Constructors
77 /// <summary>
78 /// Initializes a new instance of the <see cref="WizardStepPage"/> class.
79 /// </summary>
80 public WizardStepPage()
84 /// <summary>
85 /// Initializes a new instance of the <see cref="WizardStepPage"/> class.
86 /// </summary>
87 /// <param name="binder">The binder.</param>
88 public WizardStepPage(IDataBinder binder) : base(binder)
92 #endregion
94 #region Useful Properties
96 /// <summary>
97 /// Gets the wizard controller.
98 /// </summary>
99 /// <value>The wizard controller.</value>
100 public IWizardController WizardController
102 get { return wizardParentController; }
103 set { wizardParentController = value; }
106 /// <summary>
107 /// Gets the controller context.
108 /// </summary>
109 /// <value>The controller context.</value>
110 public IControllerContext WizardControllerContext
112 get { return wizardcontrollerContext; }
113 set { wizardcontrollerContext = value; }
116 #endregion
118 #region Core Lifecycle methods
120 /// <summary>
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.
124 /// </summary>
125 public virtual void Reset()
129 /// <summary>
130 /// Returns the action name that will be used
131 /// to represent this step.
132 /// </summary>
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;
150 /// <summary>
151 /// Used to decide on which view to render.
152 /// </summary>
153 protected internal virtual void RenderWizardView()
155 RenderView(ActionName);
158 /// <summary>
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.
163 /// </summary>
164 /// <returns></returns>
165 public virtual bool IsPreConditionSatisfied(IEngineContext context)
167 return true;
170 /// <summary>
171 /// Uses a simple heuristic to select the best method -- especially in the
172 /// case of method overloads.
173 /// </summary>
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);
185 else
187 return base.SelectMethod(action, actions, request, actionArgs);
191 #endregion
193 /// <summary>
194 /// Override takes care of selecting the wizard parent layout as default
195 /// layout if no layout is attached to the step
196 /// </summary>
197 protected override void ResolveLayout()
199 base.ResolveLayout();
201 if (LayoutName == null)
203 LayoutNames = wizardcontrollerContext.LayoutNames;
207 #region DoNavigate and Redirects
209 /// <summary>
210 /// Navigates within the wizard steps using optionally a form parameter
211 /// to dictate to where it should go.
212 /// </summary>
213 /// <remarks>
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>
224 /// </list>
225 /// </remarks>
226 protected void DoNavigate()
228 DoNavigate((IDictionary) null);
231 /// <summary>
232 /// Navigates within the wizard steps using optionally a form parameter
233 /// to dictate to where it should go.
234 /// </summary>
235 /// <remarks>
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>
246 /// </list>
247 /// </remarks>
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));
254 /// <summary>
255 /// Navigates within the wizard steps using optionally a form parameter
256 /// to dictate to where it should go.
257 /// </summary>
258 /// <remarks>
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>
269 /// </list>
270 /// </remarks>
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);
294 else
296 RedirectToStep(navigateTo, queryStringParameters);
300 /// <summary>
301 /// Sends a redirect to the next wizard step (if it exists)
302 /// </summary>
303 /// <exception cref="MonoRailException">if no further step exists</exception>
304 protected void RedirectToNextStep()
306 RedirectToNextStep((IDictionary) null);
309 /// <summary>
310 /// Sends a redirect to the next wizard step (if it exists)
311 /// </summary>
312 /// <exception cref="MonoRailException">if no further step exists</exception>
313 protected void RedirectToNextStep(params String[] queryStringParameters)
315 RedirectToNextStep(DictHelper.Create(queryStringParameters));
318 /// <summary>
319 /// Sends a redirect to the next wizard step (if it exists)
320 /// </summary>
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);
340 else
342 throw new MonoRailException("There is no next step available");
346 /// <summary>
347 /// Sends a redirect to the previous wizard step
348 /// </summary>
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);
356 /// <summary>
357 /// Sends a redirect to the previous wizard step
358 /// </summary>
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));
366 /// <summary>
367 /// Sends a redirect to the previous wizard step
368 /// </summary>
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);
387 else
389 throw new MonoRailException("There is no previous step available");
393 /// <summary>
394 /// Sends a redirect to the first wizard step
395 /// </summary>
396 protected void RedirectToFirstStep()
398 RedirectToFirstStep((IDictionary) null);
401 /// <summary>
402 /// Sends a redirect to the first wizard step
403 /// </summary>
404 protected void RedirectToFirstStep(params String[] queryStringParameters)
406 RedirectToFirstStep(DictHelper.Create(queryStringParameters));
409 /// <summary>
410 /// Sends a redirect to the first wizard step
411 /// </summary>
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);
421 /// <summary>
422 /// Sends a redirect to a custom step (that must exists)
423 /// </summary>
424 protected bool RedirectToStep(String stepName)
426 return RedirectToStep(stepName, (IDictionary) null);
429 /// <summary>
430 /// Sends a redirect to a custom step (that must exists)
431 /// </summary>
432 protected bool RedirectToStep(String stepName, params String[] queryStringParameters)
434 return RedirectToStep(stepName, DictHelper.Create(queryStringParameters));
437 /// <summary>
438 /// Sends a redirect to a custom step (that must exists)
439 /// </summary>
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);
451 return true;
455 return false;
458 /// <summary>
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.
462 /// </summary>
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);
488 else
490 Redirect(WizardControllerContext.AreaName, wizardcontrollerContext.Name, step);
494 #endregion