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
;
19 using System
.Reflection
;
21 using Castle
.Components
.Binder
;
22 using Castle
.MonoRail
.Framework
.Helpers
;
23 using Castle
.MonoRail
.Framework
.Internal
;
26 /// Represents a wizard step. In essence it is a controller, but with some subtle differences.
27 /// See the remarks for more information.
30 /// <seealso cref="WizardActionProvider"/>
31 /// <seealso cref="IWizardController"/>
34 /// Implementors can optionally override <see cref="WizardStepPage.ActionName"/>
35 /// to customize the accessible action name and
36 /// <see cref="WizardStepPage.RenderWizardView"/> in order to define which view
37 /// should be used (defaults to the step name)
40 /// Please note that an step might have actions as well, but it follows a different
41 /// convention to be accessed. You must use the wizard controller name, slash, the
42 /// step name, hifen, the action name. For example <c>/MyWizard/AddressInformation-GetCountries.rails</c>
43 /// Which would access the following action
47 /// public class AddressInformation : WizardStepPage
49 /// public void GetCountries()
56 /// Note that the RedirectToAction will always send to an internal action, so you should
57 /// omit the controller name for that.
61 /// You can use a family of redirect methods to go back and forward on the wizard's
65 public abstract class WizardStepPage
: SmartDispatcherController
69 private Controller _wizardcontroller
;
76 /// Initializes a new instance of the <see cref="WizardStepPage"/> class.
78 public WizardStepPage()
83 /// Initializes a new instance of the <see cref="WizardStepPage"/> class.
85 /// <param name="binder">The binder.</param>
86 public WizardStepPage(IDataBinder binder
) : base(binder
)
92 #region Useful Properties
95 /// Gets the wizard controller.
97 /// <value>The wizard controller.</value>
98 public Controller WizardController
100 get { return _wizardcontroller; }
105 #region Core Lifecycle methods
108 /// Invoked by <see cref="WizardActionProvider"/>.
111 /// This can be overriden but it's important to invoke the base
114 /// <param name="wizardController"></param>
115 protected internal virtual void Initialize(Controller wizardController
)
117 _wizardcontroller
= wizardController
;
119 PropertyBag
= wizardController
.PropertyBag
;
123 /// Invoked when the wizard is being access from the start
124 /// action. Implementors should perform session clean up (if
125 /// they actually use the session) to avoid stale data on forms.
127 protected internal virtual void Reset()
132 /// Returns the action name that will be used
133 /// to represent this step.
135 public virtual String ActionName
139 Type thisType
= GetType();
141 // Hack fix for "dynamic proxied" controllers
142 if (thisType
.Assembly
.FullName
.StartsWith("DynamicAssemblyProxyGen") ||
143 thisType
.Assembly
.FullName
.StartsWith("DynamicProxyGenAssembly2"))
145 return thisType
.BaseType
.Name
;
148 return GetType().Name
;
153 /// Used to decide on which view to render.
155 protected internal virtual void RenderWizardView()
157 RenderView(ActionName
);
161 /// Allow the step to assert some condition
162 /// before being accessed. Returning <c>false</c>
163 /// prevents the step from being processed but
164 /// before doing that you must send a redirect.
166 /// <returns></returns>
167 protected internal virtual bool IsPreConditionSatisfied(IRailsEngineContext context
)
173 /// Uses a simple heuristic to select the best method -- especially in the
174 /// case of method overloads.
176 /// <param name="action">The action name</param>
177 /// <param name="actions">The avaliable actions</param>
178 /// <param name="request">The request instance</param>
179 /// <param name="actionArgs">The custom arguments for the action</param>
180 /// <returns></returns>
181 protected internal override MethodInfo
SelectMethod(String action
, IDictionary actions
, IRequest request
, IDictionary actionArgs
)
183 if (action
== "RenderWizardView")
185 return typeof(WizardStepPage
).GetMethod("RenderWizardView", BindingFlags
.Instance
|BindingFlags
.NonPublic
);
189 return base.SelectMethod(action
, actions
, request
, null);
195 #region DoNavigate and Redirects
198 /// Navigates within the wizard steps using optionally a form parameter
199 /// to dictate to where it should go.
202 /// By default this will invoke <see cref="RedirectToNextStep(IDictionary)"/>
203 /// however you can send a field form <c>navigate.to</c> to customize this.
204 /// The possible values for <c>navigate.to</c> are:
205 /// <list type="bullet">
206 /// <item><term>previous</term>
207 /// <description>Invokes <see cref="RedirectToPreviousStep()"/></description></item>
208 /// <item><term>first</term>
209 /// <description>Invokes <see cref="RedirectToFirstStep()"/></description></item>
210 /// <item><term>step name</term>
211 /// <description>A custom step name to navigate</description></item>
214 protected void DoNavigate()
216 DoNavigate((IDictionary
) null);
220 /// Navigates within the wizard steps using optionally a form parameter
221 /// to dictate to where it should go.
224 /// By default this will invoke <see cref="RedirectToNextStep(IDictionary)"/>
225 /// however you can send a field form <c>navigate.to</c> to customize this.
226 /// The possible values for <c>navigate.to</c> are:
227 /// <list type="bullet">
228 /// <item><term>previous</term>
229 /// <description>Invokes <see cref="RedirectToPreviousStep()"/></description></item>
230 /// <item><term>first</term>
231 /// <description>Invokes <see cref="RedirectToFirstStep()"/></description></item>
232 /// <item><term>step name</term>
233 /// <description>A custom step name to navigate</description></item>
236 /// <param name="queryStringParameters">Query string parameters to be on the URL</param>
237 protected void DoNavigate(params String
[] queryStringParameters
)
239 DoNavigate(DictHelper
.Create(queryStringParameters
));
243 /// Navigates within the wizard steps using optionally a form parameter
244 /// to dictate to where it should go.
247 /// By default this will invoke <see cref="RedirectToNextStep(IDictionary)"/>
248 /// however you can send a field form <c>navigate.to</c> to customize this.
249 /// The possible values for <c>navigate.to</c> are:
250 /// <list type="bullet">
251 /// <item><term>previous</term>
252 /// <description>Invokes <see cref="RedirectToPreviousStep()"/></description></item>
253 /// <item><term>first</term>
254 /// <description>Invokes <see cref="RedirectToFirstStep()"/></description></item>
255 /// <item><term>step name</term>
256 /// <description>A custom step name to navigate</description></item>
259 /// <param name="queryStringParameters">Query string parameters to be on the URL</param>
260 protected void DoNavigate(IDictionary queryStringParameters
)
262 string uriPrefix
= "uri:";
264 String navigateTo
= Params
["navigate.to"];
266 if (navigateTo
== "previous")
268 RedirectToPreviousStep(queryStringParameters
);
270 else if (navigateTo
== null || navigateTo
== String
.Empty
|| navigateTo
== "next")
272 RedirectToNextStep(queryStringParameters
);
274 else if (navigateTo
.StartsWith(uriPrefix
))
276 Redirect(navigateTo
.Substring(uriPrefix
.Length
), queryStringParameters
);
278 else if (navigateTo
== "first")
280 RedirectToFirstStep(queryStringParameters
);
284 RedirectToStep(navigateTo
, queryStringParameters
);
289 /// Sends a redirect to the next wizard step (if it exists)
291 /// <exception cref="RailsException">if no further step exists</exception>
292 protected void RedirectToNextStep()
294 RedirectToNextStep((IDictionary
) null);
298 /// Sends a redirect to the next wizard step (if it exists)
300 /// <exception cref="RailsException">if no further step exists</exception>
301 protected void RedirectToNextStep(params String
[] queryStringParameters
)
303 RedirectToNextStep(DictHelper
.Create(queryStringParameters
));
307 /// Sends a redirect to the next wizard step (if it exists)
309 /// <exception cref="RailsException">if no further step exists</exception>
310 protected void RedirectToNextStep(IDictionary queryStringParameters
)
312 String wizardName
= WizardUtils
.ConstructWizardNamespace(_wizardcontroller
);
314 int currentIndex
= (int) Context
.Session
[wizardName
+ "currentstepindex"];
316 IList stepList
= (IList
) Context
.Items
["wizard.step.list"];
318 if ((currentIndex
+ 1) < stepList
.Count
)
320 int nextStepIndex
= currentIndex
+ 1;
322 String nextStep
= (String
) stepList
[nextStepIndex
];
324 WizardUtils
.RegisterCurrentStepInfo(_wizardcontroller
, nextStepIndex
, nextStep
);
326 InternalRedirectToStep(nextStepIndex
, nextStep
, queryStringParameters
);
330 throw new RailsException("There is no next step available");
335 /// Sends a redirect to the previous wizard step
337 /// <exception cref="RailsException">
338 /// if no previous step exists (ie. already in the first one)</exception>
339 protected void RedirectToPreviousStep()
341 RedirectToPreviousStep((IDictionary
) null);
345 /// Sends a redirect to the previous wizard step
347 /// <exception cref="RailsException">
348 /// if no previous step exists (ie. already in the first one)</exception>
349 protected void RedirectToPreviousStep(params String
[] queryStringParameters
)
351 RedirectToPreviousStep(DictHelper
.Create(queryStringParameters
));
355 /// Sends a redirect to the previous wizard step
357 /// <exception cref="RailsException">
358 /// if no previous step exists (ie. already in the first one)</exception>
359 protected void RedirectToPreviousStep(IDictionary queryStringParameters
)
361 String wizardName
= WizardUtils
.ConstructWizardNamespace(_wizardcontroller
);
363 int currentIndex
= (int) Context
.Session
[wizardName
+ "currentstepindex"];
365 IList stepList
= (IList
) Context
.Items
["wizard.step.list"];
367 if ((currentIndex
- 1) >= 0)
369 int prevStepIndex
= currentIndex
- 1;
371 String prevStep
= (String
) stepList
[prevStepIndex
];
373 InternalRedirectToStep(prevStepIndex
, prevStep
, queryStringParameters
);
377 throw new RailsException("There is no previous step available");
382 /// Sends a redirect to the first wizard step
384 protected void RedirectToFirstStep()
386 RedirectToFirstStep((IDictionary
) null);
390 /// Sends a redirect to the first wizard step
392 protected void RedirectToFirstStep(params String
[] queryStringParameters
)
394 RedirectToFirstStep(DictHelper
.Create(queryStringParameters
));
398 /// Sends a redirect to the first wizard step
400 protected void RedirectToFirstStep(IDictionary queryStringParameters
)
402 IList stepList
= (IList
) Context
.Items
["wizard.step.list"];
404 String firstStep
= (String
) stepList
[0];
406 InternalRedirectToStep(0, firstStep
, queryStringParameters
);
410 /// Sends a redirect to a custom step (that must exists)
412 protected bool RedirectToStep(String stepName
)
414 return RedirectToStep(stepName
, (IDictionary
) null);
418 /// Sends a redirect to a custom step (that must exists)
420 protected bool RedirectToStep(String stepName
, params String
[] queryStringParameters
)
422 return RedirectToStep(stepName
, DictHelper
.Create(queryStringParameters
));
426 /// Sends a redirect to a custom step (that must exists)
428 protected bool RedirectToStep(String stepName
, IDictionary queryStringParameters
)
430 IList stepList
= (IList
) Context
.Items
["wizard.step.list"];
432 for(int index
= 0; index
< stepList
.Count
; index
++)
434 String curStep
= (String
) stepList
[index
];
436 if (curStep
== stepName
)
438 InternalRedirectToStep(index
, stepName
, queryStringParameters
);
447 /// For a wizard step, an internal action will always be named
448 /// with the controller name as a prefix , plus an hifen and finally
449 /// the action name. This implementation does exactly that.
451 /// <param name="action">Raw action name</param>
452 /// <returns>Properly formatted action name</returns>
453 internal override String
TransformActionName(String action
)
455 return base.TransformActionName(ActionName
+ "-" + action
);
458 private void InternalRedirectToStep(int stepIndex
, String step
, IDictionary queryStringParameters
)
460 WizardUtils
.RegisterCurrentStepInfo(_wizardcontroller
, stepIndex
, step
);
462 if (queryStringParameters
!= null && queryStringParameters
.Count
!= 0)
464 Redirect(_wizardcontroller
.Name
, step
, queryStringParameters
);
466 else if (Context
.Request
.QueryString
.HasKeys())
468 // We need to preserve any attribute from the QueryString
469 // for example in case the url has an Id
471 string url
= UrlBuilder
.BuildUrl(Context
.UrlInfo
, _wizardcontroller
.Name
, step
) + Context
.Request
.Uri
.Query
;
473 Context
.Response
.Redirect(url
);
477 Context
.Response
.Redirect(_wizardcontroller
.Name
, step
);