Table of Contents
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.
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.
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
We refer to action the procedures that can be invoked on your controller. Basically it translates to any public instance method your controller exposes.
If you do not want that a specific method be "invocable", it cannot be public.
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");
}
}
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:
| Name | Description |
|---|---|
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). |
Request/Response
| Property | Type | Description |
|---|---|---|
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
| Property | Type | Description |
|---|---|---|
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
| Property | Type | Description |
|---|---|---|
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. |
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.
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.
Since the RC2 release empty strings are converted to
null
strings.
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)
{
...
}
}
Nullables data types are also supported. They will only be populated if the values are present on the form and in non-empty fields.
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)
{
...
}
}
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)
{
}
}
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; }
}
}
Your class must have a default parameterless constructor.
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.
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 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
}
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)
{
...
}
}
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 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);
}
...
}
}
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");
...
}
...
}
}
The following types are natively supported by the
DataBinder
component:
| Type name | Note |
|---|---|
| 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. |
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.
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:

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