// 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 Castle.MonoRail.Framework.Helpers;
using Castle.MonoRail.Framework.Internal;
using Castle.MonoRail.Framework.Descriptors;
///
/// Provide easy to use Wizard-like support.
///
///
///
/// MonoRail uses the DynamicAction infrastructure to provide
/// wizard support so we dont force
/// the programmer to inherit from a specific Controller
/// which can be quite undesirable in real world projects.
///
/// Nevertheless we do require that the programmer
/// implements on the wizard controller.
///
///
public class WizardActionProvider : IDynamicActionProvider, IDynamicAction
{
private IWizardStepPage[] steps;
private IWizardStepPage currentStepInstance;
private String rawAction;
private String innerAction;
private String requestedAction;
private UrlInfo urlInfo;
///
/// Implementation of IDynamicActionProvider.
///
/// Grab all steps related to the wizard
/// and register them as dynamic actions.
///
///
/// The engine context.
/// Wizard controller (must implement
/// The controller context.
public void IncludeActions(IEngineContext engineContext, IController controller, IControllerContext controllerContext)
{
// Primordial assert
IWizardController wizardController = controller as IWizardController;
if (wizardController == null)
{
throw new MonoRailException("The controller {0} must implement the interface " +
"IWizardController to be used as a wizard", controllerContext.Name);
}
// Grab all Wizard Steps
steps = wizardController.GetSteps(engineContext);
if (steps == null || steps.Length == 0)
{
throw new MonoRailException("The controller {0} returned no WizardStepPage", controllerContext.Name);
}
List stepList = new List();
// Include the "start" dynamic action, which resets the wizard state
controllerContext.DynamicActions["start"] = this;
// Find out the action request (and possible inner action)
// Each action will be a step name, or maybe the step name + action (ie Page1-Save)
urlInfo = engineContext.UrlInfo;
rawAction = urlInfo.Action;
requestedAction = ObtainRequestedAction(rawAction, out innerAction);
// If no inner action was found, fallback to 'RenderWizardView'
if (string.IsNullOrEmpty(innerAction))
{
innerAction = "RenderWizardView";
}
engineContext.Items["wizard.step.list"] = stepList;
SetUpWizardHelper(engineContext, controller, controllerContext);
// Initialize all steps and while we are at it,
// discover the current step
foreach(IWizardStepPage step in steps)
{
string actionName = step.ActionName;
step.WizardController = wizardController;
step.WizardControllerContext = controllerContext;
if (string.Compare(requestedAction, actionName, true) == 0)
{
currentStepInstance = step;
if (innerAction != null)
{
// If there's an inner action, we invoke it as a step too
controllerContext.DynamicActions[rawAction] = new DelegateDynamicAction(OnStepActionRequested);
}
engineContext.CurrentController = step;
}
else
{
controllerContext.DynamicActions[actionName] = new DelegateDynamicAction(OnStepActionRequested);
}
stepList.Add(actionName);
}
SetUpWizardHelper(engineContext, controller, controllerContext);
}
///
/// Invoked as "start" action
///
///
public object Execute(IEngineContext engineContext, IController controller, IControllerContext controllerContext)
{
StartWizard(engineContext, controller, controllerContext, true);
return null;
}
///
/// Invoked when a step is accessed on the url,
/// i.e. http://host/mywizard/firststep.rails and
/// when an inner action is invoked like http://host/mywizard/firststep-save.rails
///
/// The engine context.
/// The controller.
/// The controller context.
private object OnStepActionRequested(IEngineContext engineContext, IController controller, IControllerContext controllerContext)
{
if (currentStepInstance != null && !HasRequiredSessionData(engineContext, controller, controllerContext))
{
StartWizard(engineContext, controller, controllerContext, false);
}
controllerContext.SelectedViewName = null;
IWizardController wizController = (IWizardController) controller;
String wizardName = WizardUtils.ConstructWizardNamespace(controllerContext);
String currentStep = (String) engineContext.Session[wizardName + "currentstep"];
// If OnBeforeStep returns false we stop
if (!wizController.OnBeforeStep(wizardName, currentStep, currentStepInstance))
{
return null;
}
ControllerMetaDescriptor stepMetaDescriptor =
engineContext.Services.ControllerDescriptorProvider.BuildDescriptor(currentStepInstance);
// Record the step we're working with
WizardUtils.RegisterCurrentStepInfo(engineContext, controller, controllerContext, currentStepInstance.ActionName);
try
{
IControllerContext stepContext =
engineContext.Services.ControllerContextFactory.Create(
controllerContext.AreaName, controllerContext.Name, innerAction,
stepMetaDescriptor, controllerContext.RouteMatch);
stepContext.PropertyBag = controllerContext.PropertyBag;
SetUpWizardHelper(engineContext, currentStepInstance, stepContext);
// IsPreConditionSatisfied might need the controller's context
if (currentStepInstance is Controller)
{
((Controller)currentStepInstance).Contextualize(engineContext, stepContext);
}
// The step cannot be accessed in the current state of matters
if (!currentStepInstance.IsPreConditionSatisfied(engineContext))
{
return null;
}
currentStepInstance.Process(engineContext, stepContext);
return null;
}
finally
{
wizController.OnAfterStep(wizardName, currentStep, currentStepInstance);
}
}
///
/// Represents an empty (no-op) action.
///
/// The controller.
protected void EmptyAction(Controller controller)
{
controller.CancelView();
}
///
/// Determines whether all wizard specific information is on the user session.
///
/// The engine context.
/// The controller.
/// The controller context.
///
/// true if has session data; otherwise, false.
///
protected bool HasRequiredSessionData(IEngineContext engineContext, IController controller, IControllerContext controllerContext)
{
String wizardName = WizardUtils.ConstructWizardNamespace(controllerContext);
return (engineContext.Session.Contains(wizardName + "currentstepindex") &&
engineContext.Session.Contains(wizardName + "currentstep"));
}
///
/// Starts the wizard by adding the required information to the
/// session and invoking
/// and detecting the first step.
///
/// The engine context.
/// The controller.
/// The controller context.
/// if set to true, a redirect
/// will be issued to the first step.
protected void StartWizard(IEngineContext engineContext, IController controller, IControllerContext controllerContext, bool redirect)
{
ResetSteps(engineContext, controller);
IWizardController wizardController = (IWizardController) controller;
IList stepList = (IList) engineContext.Items["wizard.step.list"];
String firstStep = (String) stepList[0];
String wizardName = WizardUtils.ConstructWizardNamespace(controllerContext);
engineContext.Session[wizardName + "currentstepindex"] = 0;
engineContext.Session[wizardName + "currentstep"] = firstStep;
wizardController.OnWizardStart();
if (redirect)
{
engineContext.Response.Redirect(controllerContext.AreaName, controllerContext.Name, firstStep);
}
}
///
/// Resets the steps by invoking
/// on all steps instances.
///
/// The engine context.
/// The controller.
protected void ResetSteps(IEngineContext engineContext, IController controller)
{
IWizardController wizardController = (IWizardController) controller;
IWizardStepPage[] steps = wizardController.GetSteps(engineContext);
foreach(IWizardStepPage step in steps)
{
step.Reset();
}
}
private String ObtainRequestedAction(String action, out String innerAction)
{
innerAction = null;
int index = action.IndexOf('-');
if (index != -1)
{
innerAction = action.Substring(index + 1);
return action.Substring(0, index);
}
return action;
}
private void SetUpWizardHelper(IEngineContext engineContext, IController controller, IControllerContext controllerContext)
{
if (controller == null) return;
WizardHelper helper = new WizardHelper();
helper.SetContext(engineContext);
helper.SetController(controller, controllerContext);
controllerContext.Helpers.Add(helper);
}
}
}