Chapter 5. Controllers

Table of Contents

5.1. Introduction
5.2. Naming Convention
5.3. Areas
5.4. Actions
5.4.1. Default Action
5.5. Redirecting
5.6. Other Useful Properties
5.7. Data Binding
5.7.1. The SmartDispatchController
5.7.2. Simple Parameter Binding
DateTime Properties
Nullable Support
Array Support
5.7.3. Custom Binding
The DataBind Attribute
Nested Objects
Array Support
Generic Lists
Setting the Binding Source
Defining Accessible Properties
Binding Errors
BindObject and BindObjectInstance
Supported Types
FormHelper
5.8. Wizards
5.8.1. Wizard Controllers
5.8.2. Wizard Action Provider
5.8.3. Steps
5.8.4. Nested Actions
5.8.5. DoNavigate
5.8.6. Conditional Steps
5.8.7. The WizardHelper
5.8.8. Windsor Integration

5.1. Introduction

Controllers are extremely important as they are the intelligent piece of the application that orchestrate the application flow. The Getting started section depicted the basic usage of controllers.

5.2. Naming Convention

The class name is used by default as the controller identification. If your controller's name ends with Controller , it will be stripped from the name. You can also use the ControllerDetails attribute to associate a different name to your controller class.

It is advisable that controller classes follow the Name + Controller suffix convention. When the controller is registered, MonoRail strips the suffix and uses the name as the key. The controller name is used on the URL to access it.

The attribute ControllerDetailsAttribute can be used to force the definition of a name to the controller. For example:

using Castle.MonoRail.Framework;

[ControllerDetails("cust")] 
public class Customer : Controller 
{

}

The controller defined above will be accessible using cust on the url.

5.3. Areas

MonoRail supports the concept of areas, which are logic groups of controllers. All controllers belongs to an area. The default area is an empty (unnamed) one.

You can think of a tree of controllers. Each node is an area, each leaf is a controller.

To define an area, use the ControllerDetailsAttribute attribute:

using Castle.MonoRail.Framework;

[ControllerDetails(Area="admin")] 
public class UsersController : Controller 
{
	public void Index() 
	{ 
	} 
}

This controller now is accessible using the following url path:

/admin/users/index.rails

You can also define more than one level:

using Castle.MonoRail.Framework;

[ControllerDetails(Area="admin/users")] 
public class PasswordMngController : Controller 
{ 
	public void Index() 
	{ 
	}
}

This controller now is accessible using the following url path:

/admin/users/passwordmng/index.rails

5.4. Actions

We refer to action the procedures that can be invoked on your controller. Basically it translates to any public instance method your controller exposes.

Note

If you do not want that a specific method be "invocable", it cannot be public.

5.4.1. Default Action

The DefaultActionAttribute attribute provides a way to associate a default action method that will be called if a matching action method can not be found. One possible use is so that a web designer can add views without the need for a developer to add new action methods. To associate a default action with your controller, use the DefaultActionAttribute attribute. This attribute can only be applied at the class level.

Unless you specify which action should be invoked if none is matched, DefaultAction will be used.

[DefaultAction] 
public class HomeController : Controller
{
	public void Index() 
	{
	}

	public void DefaultAction() 
	{ 
		string template = "notfound";

		if (HasTemplate("home/" + Action)) 
		{ 
			template = Action;
		}

		RenderView(template); 
	} 
}

In the following example, the code specifies the action to be invoked

[
DefaultAction("Foo")] 
public class HomeController : Controller 
{ 
	public void Index() 
	{ 
	}

	public void Foo() 
	{ 
		RenderText(Action + " was not found"); 
	}
}

5.5. Redirecting

The Controller offers a handful of Redirect overloads. Some of them allow you to pass along query string parameters.

The following table list some of the overloads:

NameDescription
RedirectToAction(string action) Redirects to another action in the same controller.
RedirectToAction(String action, params String[] queryStringParameters) Redirects to another action in the same controller specifying query string entries.
RedirectToAction(String action, IDictionary parameters) Redirects to another action in the same controller specifying query string entries.
Redirect(String url) Redirects to the specified URL.
Redirect(String url, IDictionary parameters) Redirects to the specified URL specifying query string entries.
Redirect(String controller, String action) Redirects to another controller and action.
Redirect(String area, String controller, String action) Redirects to another controller and action (within an area).

5.6. Other Useful Properties

Request/Response

PropertyTypeDescription
Context Castle.MonoRail.Framework.IRailsEngineContext Gets the context of this request execution.
Session IDictionary Gets the Session dictionary.
Flash Castle.MonoRail.Framework.Flash Gets a dictionary of volative items. Ideal for showing success and failures messages.
HttpContext System.Web.HttpContext Gets the web context of ASP.NET API.
Request Castle.MonoRail.Framework.IRequest Gets the request object.
Response Castle.MonoRail.Framework.IResponse Gets the response object.
Params NameValueCollection Shortcut to IRequest.Params
Form NameValueCollection Shortcut to IRequest.Form
Query NameValueCollection Shortcut to IRequest.QueryString
IsClientConnected bool Shortcut to IResponse.IsClientConnected

Controller information

PropertyTypeDescription
Name string Gets the controller's name (as MonoRail knows it)
AreaName string Gets the controller's area name.
LayoutName string Gets or set the layout being used.
Action string Gets the name of the action being processed.
SelectedViewName string Gets or sets the view which will be rendered by this action. We encourage you to use RenderView or RenderSharedView instead of setting this property.

Others

PropertyTypeDescription
Logger Castle.Core.Logging.ILogger Logger for the controller (you must enable logging first)
IsPostBack bool Determines if the current Action resulted from an ASP.NET PostBack. As a result, this property is only relavent when using WebForms views. It is placed on the base Controller for convenience only to avoid the need to extend the Controller or provide additional helper classes.

5.7. Data Binding

The SmartDispatcherController extends Controller class adding support for parameter binding. This allow you to bind parameters from form elements to your action arguments. Overloads are also supported. MonoRail will invoke the action that it can supply more parameters.

MonoRail is able to bind simple values and complex objects. Both approaches are described in the sections below.

5.7.1. The SmartDispatchController

5.7.2. Simple Parameter Binding

Note

A sample demonstrating the concepts in the section can be downloaded from !download/monorail/trunk/MonoRail.SimpleBindingSample.zip

Consider the following html form:

<form action="/User/Search.rails" method="post">
	Name: <input type="text" name="name" /> 
	Email: <input type="text" name="email" /> 
	Country: 
	<select name="country"> 
		<option	value="44">England</option> 
		<option	value="55">Brazil</option> 
	</select>
	<input type="submit" value="Search" />
</form>

When this form is submitted, the following entries will be present on the Form dictionary:

  • name

  • email

  • country

The standard way of getting those values on the controller is to use one of the dictionaries:

  • Params : Has query string, form and environment entries

  • Form : Has only form entries (method post)

  • Query : Has only query string entries

Having said that your action code could be the following:

using Castle.MonoRail.Framework;

public class UserController : Controller 
{ 
	public void	Search() 
	{ 
		String name = Form["name"]; 
		String email = Form["email"]; 
		String country = Form["country"];

			// Perform search ... 
	} 
}

Now if you switch to SmartDispatcherController you would be able to use the following simpler code instead:

using Castle.MonoRail.Framework;

public class UserController : SmartDispatcherController
{ 
	public void Search(string name, string email, string country) 
	{ 
		// Perform search ... 
	}
}

The SmartDispatcherController is able to perform conversions (more on that below). In this case if the value is not present (ie. it was not submitted), the argument will assume a default value. However, if the value was submitted, but could not be converted, an exception will be thrown and the action will not be invoked.

Note

Since the RC2 release empty strings are converted to null strings.

DateTime Properties

To bind DateTime fields you can pass a single value or multiple values. Each of them will be a part of the DateTime struct. For example, using a single value:

<form action="SaveValues.rails" method="post">
	<input type="text" name="dob" value="1/1/2000"/> 
</form>

Using multiple values:

<form action="SaveValues.rails" method="post">
	<input type="text" name="dobday" value="16" />
	<input type="text" name="dobmonth" value="7"/> 
	<input type="text" name="dobyear" value="1979" />
	<input type="text"name="dobhour" value="4" /> 
	<input type="text" name="dobminute" value="0" /> 
	<input type="text" name="dobsecond" value="0" />
</form>

Regardless of the form approach, the controller action parameter will be the same:

using Castle.MonoRail.Framework;

public class UserController : SmartDispatcherController 
{ 
	public void SaveValues(DateTime dob) 
	{ 
		... 
	} 
}

Nullable Support

Nullables data types are also supported. They will only be populated if the values are present on the form and in non-empty fields.

Array Support

Arrays are also supported on the controller side. You can use two naming approaches on the form elements to make it work.

The first approach is to repeat the element name. For example:

<form action="SaveValues.rails" method="post">
	<input type="text" name="name" value="1" />
	<input type="text" name="name" value="2" />
	<input type="text" name="name" value="3" />
	<input type="text" name="name" value="4" />
	<input type="text" name="name" value="5" />
</form>

The second approach is to use the indexed value notation. The index value is meaningless to MonoRail, but it must be unique per element name. For example:

<form action="SaveValues.rails" method="post">
	<input type="text" name="name[0]" value="1" />
	<input type="text" name="name[1]" value="2" />
	<input type="text" name="name[2]" value="3" />
	<input type="text" name="name[3]" value="4" />
	<input type="text" name="name[4]" value="5" />
</form>

On the controller side, the parameter will be the same independently of the approach in use. All you need to do is to use an array type:

using Castle.MonoRail.Framework;

public class UserController : SmartDispatcherController 
{ 
	public void SaveValues(string[] name) 
	{
		 ... 
	} 
}

5.7.3. Custom Binding

The DataBind Attribute

Note

A sample demonstrating the concepts in the section can be downloaded from !download/monorail/trunk/MonoRail.SimpleBindingSample.zip

If instead of working with flat values you want to populate an object, this is also possible using the DataBindAttribute .

The DataBindAttribute used the Castle.Component.Binder to instantiate and populate the target type. Simple values, nested objects and arrays are supported. As with simple binding, a name convention must be used on the form elements, so the binder can do its work.

First of all you must use a prefix which is required to avoid name clashing. It is as giving the form elements a name space. The form below uses product as a prefix:

<form method="post" action="create.rails">
	<input type="text" name="product.id" />
	<input type="text" name="product.name" />
	<input type="checkbox" name="product.inStock" id="" value="true" /> 
</form>

On the controller action you must specify the prefix as the argument to the DataBindAttribute :

using Castle.MonoRail.Framework;

public class ProductController : SmartDispatcherController 
{ 
	public void Create([DataBind("product")] Product prod) 
	{
	} 
}

Note

The parameter name (in the case above prod ) is not used by the binder and have no relation with the prefix.

The binding of values happens with writable properties only. Fields are never used. The Product class used on the example above would be:

public class Product 
{ 
	private int id; 
	private String name; 
	private bool inStock;

	public int Id 
	{ 
		get { return id; } 
		set { id = value;}
	}

	public string Name 
	{ 
		get { return name; } 
		set { name = value; } 
	}

	public bool InStock 
	{ 
		get { return inStock; } 
		set { inStock = value; } 
	} 
}

Note

Your class must have a default parameterless constructor.

Nested Objects

Nested objects are supported with no deep limit. Suppose the Product class above included a SupplierInfo :

public class Product 
{ 
	private SupplierInfo supplierInfo;

	// others fields omitted

	public SupplierInfo SupplierInfo 
	{ 
		get { return supplierInfo; } 
		set { supplierInfo = value; } 
	}

	// others properties omitted 
}

The declaration of SupplierInfo follow. Note that it uses different types, including an enumerator.

public enum WeightUnit 
{ 
	Kilos, 
	Pounds 
}

public class SupplierInfo 
{ 
	private String brand;
	private float weight; 
	private WeightUnit weightUnit;
	private int warrantyInMonths;

	public string Brand 
	{ 
		get { return brand; } 
		set { brand = value; } 
	}

	public float Weight 
	{
		get { return weight; } 
		set { weight = value; } 
	}

	public WeightUnit WeightUnit 
	{ 
		get { return weightUnit; } 
		set { weightUnit = value; } 
	}

	public int WarrantyInMonths 
	{ 
		get { return warrantyInMonths; } 
		set { warrantyInMonths = value; } 
	} 
}

When adding elements on the form, all you have to care is to include the property name. For the case above it would be:

<form method="post" action="create.rails">
	<input type="text" name="product.id" />
	<input type="text" name="product.name" />
	<input type="checkbox" name="product.inStock" id="" value="true" />

	<input type="text" name="product.supplierinfo.brand" /> 
	<input type="text" name="product.supplierinfo.Weight" />

	<select name="product.supplierinfo.WeightUnit">
		<option value="Kilos">In Kg</option>
		<option value="Pounds">In Pounds</option> 
	</select>

	<input type="text" name="product.supplierinfo.WarrantyInMonths" />

</form>

The rule is prefixname.propertyname1.propertyname2... . The binder is not case sensitive.

Array Support

There are two situations for array support. First, suppose instead of populating a single Product you would want to populate a sequence of them. This demands two changes in the example we have seen so far.

First the form elements must use the indexed notation discussed earlier:

<form method="post" action="create.rails">
	<input type="text" name="product[0].id" />
	<input type="text" name="product[0].name" />
	<input type="checkbox" name="product[0].inStock" id="" value="true" />

	<input type="text" name="product[1].id" />
	<input type="text" name="product[1].name" />
	<input type="checkbox" name="product[1].inStock" id="" value="true" /> 
</form>

Second, on the controller you must declare the parameter as an array of Product s:

using Castle.MonoRail.Framework;

public class ProductController : SmartDispatcherController 
{ 
	public void Create([DataBind("product")] Product[] prods) 
	{
	} 
}

The rule is prefixname[uniqueindex].propertyname1.propertyname2... . The index must be a number, and the same number identifies the same instance.

Another situation is when one or more properties of the binding target are arrays. This case is also supported and not different from what we have seen.

Being practical, suppose the Product class in the example above included a Category array.

public class Product 
{ 
	private Category[] categories;

	// others fields omitted

	public Category[] Categories 
	{ 
		get { return categories; } 
		set { categories = value; } 
	}

	// others properties omitted 
}

It could also be an array of simple values like String s or int s and the solution would be the same. The declaration of the Category follows:

public class Category 
{
	private String name;

	public string Name 
	{ 
		get { return name; } 
		set { name = value; } 
	} 
}

One more time the solution lies on the element names on the form. The property name must be used in the indexed notation:

<form method="post" action="create.rails">
	<input type="text" name="product.id" />
	<input type="text" name="product.name" />
	<input type="checkbox" name="product.inStock" id="" value="true" />

	<input type="checkbox" name="product.categories[0].name" value="Kitchen"/> 
	<input type="checkbox" name="product.categories[1].name" value="Bedroom"/> 
	<input type="checkbox" name="product.categories[2].name" value="Living-room" /> 
</form>

The rule is this case would be prefixname.propertyname1[uniqueindex].propertyname2... .

Generic Lists

Generic Lists are supported and the behavior is the same of the arrays, expect the property declaration of course.

public class Product 
{ 
	private List<Category> categories;

	// others fields omitted

	public List<Category> Categories 
	{ 
		get { return categories; } 
		set { categories = value; } 
	}

	// others properties omitted 
}

Setting the Binding Source

By default the binder will use the Params collection as source of information to bind data. You can define that it should use the QueryString or Form post data instead. To do that use the From property exposed by the DataBindAttribute .

This is a recommend practice for performance and to prevent people from easily override form parameters. For example:

using Castle.MonoRail.Framework;

public class ProductController : SmartDispatcherController 
{ 
	public void Create([DataBind("product", From=ParamStore.Form)] Product product) 
	{ 
		... 
	} 
}

Defining Accessible Properties

As the DataBindAttribute usually act on domain model classes, you might not want that all properties be "bindable". Suppose you are binding an User class. Sensitive properties might allow overriding the password, roles, access levels or audit information. For these cases you can use Allow and Exclude properties of DataBindAttribute .

The values for these properties are a comma separated list of property names, including the prefix. For example:

public class AccountController : SmartDispatcherController 
{ 
	public void CreateAccount([DataBind("account", Allow="account.Name, account.Email, account.Password")] Account account) 
	{ 
		... 
	} 
}

This indicates that you only want to allow the Name , Email and Password properties to bound with the values from the request. All other properties will be ignored.

The Exclude property is the inverse. It prevents the properties indicated from being used, and allow all others.

There is no depth limit. You should be able to allow or exclude properties in any level of the object graph. For example:

public class AccountController : SmartDispatcherController 
{ 
	public void CreateAccount([DataBind("account", Allow="account.Name, account.Address, account.Address.Street")] Account account) 
	{ 
		... 
	} 
}

Binding Errors

Binding errors might ocur, like invalid dates or problems in data conversion. When using simple binding, an exception will be thrown. When using the DataBindAttribute , however, no exception will be thrown.

To access the error information, use the GetDataBindErrors method:

public class AccountController : SmartDispatcherController 
{ 
	public void CreateAccount([DataBind("account")] Account account)
	{ 
		ErrorList errors = GetDataBindErrors(account);
		... 
	} 
}

The ErrorList implements the ICollection so you can enumerate the problems. You can also check if some specific property could not be converted. For example:

public class AccountController : SmartDispatcherController 
{
	public void CreateAccount([DataBind("account")] Account account) 
	{ 
		ErrorList errors = GetDataBindErrors(account);

		if (errors.Contains("DateOfBirth")) 
		{ 
			Flash["error"]= errors["DateOfBirth"].ToString(); // Or Exception
			RedirectToAction("New", Params); 
		}

		... 
	} 
}

BindObject and BindObjectInstance

You do not need to always use parameters to have an object bound. The methods BindObject and BindObjectInstance , exposed by the SmartDispatcherController , allow you to have the same functionality. The benefit is that not under every case you want to perform the bindings. For example:

public class AccountController : SmartDispatcherController 
{ 
	public void CreateAccount(bool acceptedConditions) 
	{ 
		if(acceptedConditions) 
		{ 
			Account account = (Account)BindObject(ParamStore.Form, typeof(Account),"account");
			... 
		}

		... 
	} 
}

Supported Types

The following types are natively supported by the DataBinder component:

Type nameNote
String Empty fields are converted to null strings
All types where IsPrimitive returns true -
Enum It is converted using the name or value. Flags are also supported
Decimal-
Guid-
DateTime The implementation checks for the key plus day , month , year , hour , minute and second . If none elements is found, it falls back to use DateTime.Parse on the value associated with the key.
Array-
Generic Lists-
HttpPostedFile-
TypeConverter If the type is not within the range above, the converter checks for a TypeConverter associated with it that is able to convert from a string.

FormHelper

The FormHelper was created to act together with the binder. It is able to create form elements with the right names and to obtain the existing value (if possible) saving you from populating the elements manually.

For more on the FormHelper visit the Helpers documentation pages.

5.8. 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.

Note

A sample demonstrating the concepts in the section can be downloaded from !download/monorail/trunk/MonoRail.WizardSample.zip

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

  • The wizard controller

    A controller must be a wizard parent. You can have as many wizards on a web site as you want. All you need to do is using the WizardActionProvider and implement the interface IWizardController

  • WizardActionProvider

    This is a built in action provider that manages the wizard flow per user. It takes care of starting the wizard, initialize the steps and delegating the execution to the active step

  • Steps

    Each wizard is composed of at least one step. Each step is a controller that extends from WizardStepPage .

Check the flow on the image below:

5.8.1. Wizard Controllers

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:

  • Bind the controller to the WizardActionProvider

  • Implement the interface IWizardController

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:

  • void OnWizardStart()

  • bool OnBeforeStep(string wizardName, string stepName, WizardStepPage step)

    return true if you don't want to block any step from being executed

  • void OnAfterStep(string wizardName, string stepName, WizardStepPage step)

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.

5.8.2. Wizard Action Provider

The WizardActionProvider is reponsible for:

  • Make the step's actions available

  • Using the session to store the current wizard step

  • Delegating execution to nested actions

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.

5.8.3. 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

5.8.4. 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.

5.8.5. 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:

  • If navigate.to is previous , DoNavigate will invoke RedirectToPreviousStep

  • If navigate.to is first , DoNavigate will invoke RedirectToFirstStep

  • If navigate.to is empty or not specified, DoNavigate will invoke RedirectToNextStep

  • If navigate.to starts with uri: , DoNavigate will invoke Redirect with the specified URL

  • Otherwise DoNavigate will assume that the specified text on navigate.to is a step name and will invoke InternalRedirectToStep with the specified value

5.8.6. Conditional Steps

You can associate conditions with steps TODO more on this

5.8.7. The 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]]

5.8.8. Windsor Integration

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) ] 
		}; 
	}
	...