Home

Castle Stronghold

Table of contents

  • 1 The main players
    • 1.1 The wizard controller
    • 1.2 The WizardActionProvider
    • 1.3 The steps
  • 2 Conditional steps
  • 3 WizardHelper
  • 4 Integration with Windsor

Creating Wizards

You can use wizards to present smaller chunks of information to the user, with more immediate feedback. For example, during a registration process or cart check-out you could save their objects into session at each step, and then persist to the database, at the end, when it is all valid and confirmed, instead of having to either save intermediary objects that are not in an acceptable state, or make one massive form.

MonoRail has built in support to create wizards like chained pages.

The main players

Wizard can be created easily on MonoRail, but first you must understand what entities are involved and what role they play.

Check the flow on the image below:

The wizard controller

The wizard controller is just an ordinary controller that might extend Controller or SmartDispatcherController or any other you might have in your controller hierarchy. The only two important things you must do is:

At this point you may be wondering why we have decided to expose wizard support through an interface and a dynamic action provider. The answer lie on the same reason why Dynamic action were created, we should not mess up with your controller hierarchy. If you're just playing with MonoRail for now you might not be able to foresee the problems that might arise if we introduce and force you to extend a specific controller class. In the end of the day we didnt want to copy & paste the use of filters, layouts and resources from our base controller to the wizard controller.

This architectural decision allow you to reuse (vertically) your controller hierarchy, thus not being intrusive to your controller object model.

To bind your controller to an action provider, use the attribute DynamicActionProviderAttribute:


[DynamicActionProvider(typeof(WizardActionProvider))]
public class MyWizardController : Controller, IWizardController
{ 
    ...

This action provider, when executed by the framework, will check whether the controller is implementing the interface IWizardController. So the next logical step is to properly implement it.

To do so, you must at least provide empty bodies for the following IWizardController's methods:

But you must implement the method GetSteps which is the heart of the wizard feature:


public WizardStepPage[] GetSteps(IRailsEngineContext context)
{
    return new WizardStepPage[]
    {
        new IntroductionStep(), 
        new MainInfoStep(), 
        new SubscribeStep(), 
        new ConfirmationStep(), 
        new ResultStep()
    };
}
Each step is a class that extends WizardStepPage. The order that you create the array and return it from GetSteps is the order on which the steps will be presented to the user.

The WizardActionProvider

The WizardActionProvider is reponsible for:

First, when this dynamic action is invoked by MonoRail the action start is added to the wizard controller, so you can point your browse to

http://yourhost/mywizard/start.rails

to start the wizard. This is not required, though but it is a good way to ensure the state is cleaned. If you direct the browser to any specific step, the wizard will start itself correctly if it does not find the proper entries in the session.

The steps

The GetSteps method returns an array of WizardStepPage which you should exted to create your own steps. For example, a very simple wizard step would be


public class IntroductionStep : WizardStepPage
{
}

Not surprisingly this will rely on defaults to work. So when this step is invoked it will just render the view named IntroductionStep (IntroductionStep.vm, or IntroductionStep.aspx or IntroductionStep.boo depending on the view engine you're using) which '''must be on the view folder for the MyWizard controller'''.

However WizardStepPage provide a few lifecycle methods that can be overriden:

MethodDescription
void Initialize(Controller wizardController)This can be overriden but it's important to invoke the base implementation
void Reset() 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
String ActionName { get; }If you want to customize the step name. Defaults to the step's class name
void RenderWizardView()Used to decide on which view to render

And the good news: the WizardStepPage is nothing but a class extending SmartDispatcherController, so you can (and should) create your own methods to perform the step work.

To access a step you should direct your browser to

http://yourhost/mywizard/stepname.rails

Where stepname stands for the value returned by the property ActionName defaulting to the class name. So if you want to access the IntroductionStep we just mentioned, you should use

http://yourhost/mywizard/introductionstep.rails

Nested actions

What we have seen so far is not enough to create a decent wizard. If you want to use ajax on a step for example, what do to? How to save a form from a step?

To solve these problems nested actions were introduced. Nested actions are handled by the WizardActionProvider and we're basically talking about accessing a step and then an action it holds.

Being a bit more concrete, suppose you have coded the following step:


public class AccountInfoStep : WizardStepPage
{
}

And on the view side you present a form gathering information from the user. You can create an action just like you would on any controller:


public class AccountInfoStep : WizardStepPage
{
    public void Save( ... )
    {
    }
}

Now the trick part. To access a nested action you must use

stepname-actioname

So you have to change the form action on the view to


<form action="AccountInfo-Save.rails" method="post">
...

Please note that the action is just like a regular action, so you must redirect the user at the end or provide a view to be rendered after it's execution.

DoNavigate

Using the same Save example, suppose that you want to direct the user to the next wizard step (if the data is OK). In this case you should invoke the method WizardActionProvider.DoNavigate():


public class AccountInfoStep : WizardStepPage
{
    public void Save( ... )
    {
      try
      {
        // validates the data and if it's not ok, throw an exception
        .. work work work ..

        DoNavigate();
      }
      catch(Exception ex)
      {
        Flash["error"] = ex;

        RedirectToAction(ActionName);
      }
    }
}

DoNavigate is a black box method, but it's easy to understand it. For most of the times you use it, it would be just like you were invoking the method RedirectToNextStep, however you can use a form field named navigate.to to customize where it should go:

Conditional steps

You can associate conditions with steps TODO more on this

WizardHelper

The WizardHelper is automatically added to the wizard controller and the steps. You can use it to create links to previous and next steps and to query whether there's a previous or next step.

TODO More on [[MonoRail:WizardHelper]]

Integration with Windsor

If you are using Windsor Integration, then it's up to you to make the steps components or not.

To use the steps as components, register them within the container (configuration file or via code) and code your wizard controller like this:


[DynamicActionProvider( typeof(WizardActionProvider) )]
public class MyWizardController : Controller, IWizardController
{ 
    private readonly IKernel kernel;

    public MyWizardController(IKernel kernel)
    {
        this.kernel = kernel;
    }

    public WizardStepPage[] GetSteps(IRailsEngineContext context)
    {
        return new WizardStepPage[]
        {
            (WizardStepPage) kernel[ typeof(IntroductionStep) ], 
            (WizardStepPage) kernel[ typeof(MainInfoStep) ], 
            (WizardStepPage) kernel[ typeof(SubscribeStep) ], 
            (WizardStepPage) kernel[ typeof(ConfirmationStep) ], 
            (WizardStepPage) kernel[ typeof(ResultStep) ]
        };
    }

    ...
Google
Search WWW Search castleproject.org