// Copyright 2004-2008 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
namespace Castle.MonoRail.Framework
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Castle.Components.Binder;
using Castle.MonoRail.Framework.Helpers;
using Castle.MonoRail.Framework.Internal;
using Services;
///
/// Represents a wizard step. In essence it is a controller, but with some subtle differences.
/// See the remarks for more information.
///
///
///
///
///
///
/// Implementors can optionally override
/// to customize the accessible action name and
/// in order to define which view
/// should be used (defaults to the step name)
///
///
/// Please note that an step might have actions as well, but it follows a different
/// convention to be accessed. You must use the wizard controller name, slash, the
/// step name, hifen, the action name. For example /MyWizard/AddressInformation-GetCountries.rails
/// Which would access the following action
///
///
///
/// public class AddressInformation : WizardStepPage
/// {
/// public void GetCountries()
/// {
/// ...
/// }
/// }
///
///
/// Note that the RedirectToAction will always send to an internal action, so you should
/// omit the controller name for that.
///
///
///
/// You can use a family of redirect methods to go back and forward on the wizard's
/// steps.
///
///
public abstract class WizardStepPage : SmartDispatcherController, IWizardStepPage
{
#region Fields
private IWizardController wizardParentController;
private IControllerContext wizardcontrollerContext;
#endregion
#region Constructors
///
/// Initializes a new instance of the class.
///
public WizardStepPage()
{
}
///
/// Initializes a new instance of the class.
///
/// The binder.
public WizardStepPage(IDataBinder binder) : base(binder)
{
}
#endregion
#region Useful Properties
///
/// Gets the wizard controller.
///
/// The wizard controller.
public IWizardController WizardController
{
get { return wizardParentController; }
set { wizardParentController = value; }
}
///
/// Gets the controller context.
///
/// The controller context.
public IControllerContext WizardControllerContext
{
get { return wizardcontrollerContext; }
set { wizardcontrollerContext = value; }
}
#endregion
#region Core Lifecycle methods
///
/// Invoked when the wizard is being access from the start
/// action. Implementors should perform session clean up (if
/// they actually use the session) to avoid stale data on forms.
///
public virtual void Reset()
{
}
///
/// Returns the action name that will be used
/// to represent this step.
///
public virtual String ActionName
{
get
{
Type thisType = GetType();
// Hack fix for "dynamic proxied" controllers
if (thisType.Assembly.FullName.StartsWith("DynamicAssemblyProxyGen") ||
thisType.Assembly.FullName.StartsWith("DynamicProxyGenAssembly2"))
{
return thisType.BaseType.Name;
}
return GetType().Name;
}
}
///
/// Used to decide on which view to render.
///
protected internal virtual void RenderWizardView()
{
RenderView(ActionName);
}
///
/// Allow the step to assert some condition
/// before being accessed. Returning false
/// prevents the step from being processed but
/// before doing that you must send a redirect.
///
///
public virtual bool IsPreConditionSatisfied(IEngineContext context)
{
return true;
}
///
/// Uses a simple heuristic to select the best method -- especially in the
/// case of method overloads.
///
/// The action name
/// The avaliable actions
/// The request instance
/// The custom arguments for the action
/// Type of the action.
///
protected override MethodInfo SelectMethod(string action, IDictionary actions, IRequest request, IDictionary actionArgs, ActionType actionType)
{
if (action == "RenderWizardView")
{
return typeof(WizardStepPage).GetMethod("RenderWizardView", BindingFlags.Instance | BindingFlags.NonPublic);
}
else
{
return base.SelectMethod(action, actions, request, actionArgs, actionType);
}
}
#endregion
///
/// Override takes care of selecting the wizard parent layout as default
/// layout if no layout is attached to the step
///
protected override void ResolveLayout()
{
base.ResolveLayout();
if (LayoutName == null)
{
LayoutNames = wizardcontrollerContext.LayoutNames;
}
}
#region DoNavigate and Redirects
///
/// Navigates within the wizard steps using optionally a form parameter
/// to dictate to where it should go.
///
///
/// By default this will invoke
/// however you can send a field form navigate.to to customize this.
/// The possible values for navigate.to are:
///
/// - previous
/// Invokes
/// - first
/// Invokes
/// - step name
/// A custom step name to navigate
///
///
protected virtual void DoNavigate()
{
DoNavigate((IDictionary) null);
}
///
/// Navigates within the wizard steps using optionally a form parameter
/// to dictate to where it should go.
///
///
/// By default this will invoke
/// however you can send a field form navigate.to to customize this.
/// The possible values for navigate.to are:
///
/// - previous
/// Invokes
/// - first
/// Invokes
/// - step name
/// A custom step name to navigate
///
///
/// Query string parameters to be on the URL
protected virtual void DoNavigate(params String[] queryStringParameters)
{
DoNavigate(DictHelper.Create(queryStringParameters));
}
///
/// Navigates within the wizard steps using optionally a form parameter
/// to dictate to where it should go.
///
///
/// By default this will invoke
/// however you can send a field form navigate.to to customize this.
/// The possible values for navigate.to are:
///
/// - previous
/// Invokes
/// - first
/// Invokes
/// - step name
/// A custom step name to navigate
///
///
/// Query string parameters to be on the URL
protected virtual void DoNavigate(IDictionary queryStringParameters)
{
string uriPrefix = "uri:";
String navigateTo = Params["navigate.to"];
if (navigateTo == "previous")
{
RedirectToPreviousStep(queryStringParameters);
}
else if (navigateTo == null || navigateTo == String.Empty || navigateTo == "next")
{
RedirectToNextStep(queryStringParameters);
}
else if (navigateTo.StartsWith(uriPrefix))
{
RedirectToUrl(navigateTo.Substring(uriPrefix.Length), queryStringParameters);
}
else if (navigateTo == "first")
{
RedirectToFirstStep(queryStringParameters);
}
else
{
RedirectToStep(navigateTo, queryStringParameters);
}
}
///
/// Sends a redirect to the next wizard step (if it exists)
///
/// if no further step exists
protected virtual void RedirectToNextStep()
{
RedirectToNextStep((IDictionary) null);
}
///
/// Sends a redirect to the next wizard step (if it exists)
///
/// if no further step exists
protected virtual void RedirectToNextStep(params String[] queryStringParameters)
{
RedirectToNextStep(DictHelper.Create(queryStringParameters));
}
///
/// Sends a redirect to the next wizard step (if it exists)
///
/// if no further step exists
protected virtual void RedirectToNextStep(IDictionary queryStringParameters)
{
String wizardName = WizardUtils.ConstructWizardNamespace(ControllerContext);
int currentIndex = (int) Context.Session[wizardName + "currentstepindex"];
IList stepList = (IList) Context.Items["wizard.step.list"];
if ((currentIndex + 1) < stepList.Count)
{
int nextStepIndex = currentIndex + 1;
String nextStep = (String) stepList[nextStepIndex];
WizardUtils.RegisterCurrentStepInfo(Context, wizardParentController, ControllerContext, nextStepIndex, nextStep);
InternalRedirectToStep(Context, nextStepIndex, nextStep, queryStringParameters);
}
else
{
throw new MonoRailException("There is no next step available");
}
}
///
/// Sends a redirect to the previous wizard step
///
///
/// if no previous step exists (ie. already in the first one)
protected virtual void RedirectToPreviousStep()
{
RedirectToPreviousStep((IDictionary) null);
}
///
/// Sends a redirect to the previous wizard step
///
///
/// if no previous step exists (ie. already in the first one)
protected virtual void RedirectToPreviousStep(params String[] queryStringParameters)
{
RedirectToPreviousStep(DictHelper.Create(queryStringParameters));
}
///
/// Sends a redirect to the previous wizard step
///
///
/// if no previous step exists (ie. already in the first one)
protected virtual void RedirectToPreviousStep(IDictionary queryStringParameters)
{
String wizardName = WizardUtils.ConstructWizardNamespace(wizardcontrollerContext);
int currentIndex = (int) Context.Session[wizardName + "currentstepindex"];
IList stepList = (IList) Context.Items["wizard.step.list"];
if ((currentIndex - 1) >= 0)
{
int prevStepIndex = currentIndex - 1;
String prevStep = (String) stepList[prevStepIndex];
InternalRedirectToStep(Context, prevStepIndex, prevStep, queryStringParameters);
}
else
{
throw new MonoRailException("There is no previous step available");
}
}
///
/// Sends a redirect to the first wizard step
///
protected virtual void RedirectToFirstStep()
{
RedirectToFirstStep((IDictionary) null);
}
///
/// Sends a redirect to the first wizard step
///
protected virtual void RedirectToFirstStep(params String[] queryStringParameters)
{
RedirectToFirstStep(DictHelper.Create(queryStringParameters));
}
///
/// Sends a redirect to the first wizard step
///
protected virtual void RedirectToFirstStep(IDictionary queryStringParameters)
{
IList stepList = (IList) Context.Items["wizard.step.list"];
String firstStep = (String) stepList[0];
InternalRedirectToStep(Context, 0, firstStep, queryStringParameters);
}
///
/// Sends a redirect to a custom step (that must exists)
///
protected virtual bool RedirectToStep(String stepName)
{
return RedirectToStep(stepName, (IDictionary) null);
}
///
/// Sends a redirect to a custom step (that must exists)
///
protected virtual bool RedirectToStep(String stepName, params String[] queryStringParameters)
{
return RedirectToStep(stepName, DictHelper.Create(queryStringParameters));
}
///
/// Sends a redirect to a custom step (that must exists)
///
protected virtual bool RedirectToStep(String stepName, IDictionary queryStringParameters)
{
IList stepList = (IList) Context.Items["wizard.step.list"];
for(int index = 0; index < stepList.Count; index++)
{
String curStep = (String) stepList[index];
if (curStep == stepName)
{
InternalRedirectToStep(Context, index, stepName, queryStringParameters);
return true;
}
}
return false;
}
///
/// For a wizard step, an internal action will always be named
/// with the controller name as a prefix , plus an hifen and finally
/// the action name. This implementation does exactly that.
///
/// Raw action name
/// Properly formatted action name
protected override String TransformActionName(String action)
{
return base.TransformActionName(ActionName + "-" + action);
}
private void InternalRedirectToStep(IEngineContext engineContext, int stepIndex, String step,
IDictionary queryStringParameters)
{
WizardUtils.RegisterCurrentStepInfo(engineContext, wizardParentController, wizardcontrollerContext, stepIndex, step);
// Does this support areas?
if (queryStringParameters != null && queryStringParameters.Count != 0)
{
Redirect(WizardControllerContext.AreaName, wizardcontrollerContext.Name, step, queryStringParameters);
}
else if (Context.Request.QueryString.HasKeys())
{
// We need to preserve any attribute from the QueryString
// for example in case the url has an Id
Redirect(WizardControllerContext.AreaName, wizardcontrollerContext.Name, step, Query);
}
else
{
Redirect(WizardControllerContext.AreaName, wizardcontrollerContext.Name, step);
}
}
#endregion
}
}