Castle demo App #2: Monorail basics

In the second part of the Castle demo application tutorial, we look into the basic features of Castle’s powerful Model-View-Controller system, called Monorail. Monorail is based on Ruby on Rails, and brings two very important features to .NET web development:

1. Good separation of concerns between the domain model, workflow logic and the user interface: this allows us to unit test larger portions of web applications, makes the code more reusable and gives us flexibility for the user interface.

2. HTTP request/response plumbing, allowing us to be much more productive when developing web pages: Monorail will automatically convert HTTP request data into strongly typed function parameters, even domain objects; it provides an infrastructure for aspect-oriented request handling and reacting to errors. That allows us to focus on the business logic of the web application and skip writing boilerplate web code.

The Model-View-Controller (MVC) pattern has stood the test of time as the best way to implement user interfaces for business applications. It asks for a clear separation of responsibilities between the domain business logic (model), workflow (controller) and the actual user interface (view). MVC has been around since the eighties, and although there are arguably more productive ways to develop user interfaces, MVC is important because it makes sure that applications will be easy to maintain. First of all, the clear separation of concerns is there to ensure that the user interface layout, typically the most frequently changed part of the system, can be updated and modified without affecting the business logic. User interface code and business code are never mixed, so we can re-use the model for several views and we can change or add views easily. Also, this separation allows us to cover a huge part of the application with automated unit tests. Automated testing through the user interface is hard to implement properly, and such tests are very brittle and generally a pain to maintain. Since MVC requires that the user interface code contains no business logic, all business code can be properly tested without even touching the UI. Monorail, in addition, allows us to unit-test the controller code easy as well (this will be explained in one of the next articles in this series). Separating workflow logic from the actual user interface display makes the code more reusable. For example, let’s say that we are working on a reporting application that allows users to view their account statements over the Web. Once the model and controllers are implemented for statements as HTML tables, changing the application to export those reports as PDFs or Excel sheets is very easy — we just need to implement another set of views. The logic behind how those views are created, authentications, data collection and filters can all be reused.

MVC is traditionally not the easiest way to create user-facing applications, but it makes sure that the applications can evolve easily and that the cost of maintenance and enhancements is low. Monorail goes one step further, and takes away all the plumbing code for web pages and makes the MVC model much more productive. We’ll come to that later, but now we need to take a step back.

A quick note before we move on: You can download the source code for this example from http://gojko.net/resources/EvilLink_i2.zip

User Registration, continued

In the previous article of this series, ActiveRecord basics and unit testing, I introduced the basic features of Castle’s database interface toolkit, ActiveRecord. We started implementing the User Registration story, and now we are going to complete it by building a set of web pages to allow users to register. In addition, to be able to check whether the users have successfully registered, we will allow them log in. In the MVC pattern, we have to separate business logic, workflow and user interface into distinct layers. For the business logic, the first story requires us to allow users to register and verify the login details. Workflow and session logic for now deal with the fact that the users should be able to register if they are not logged in, and that they will be able to log out after logging in. The user interface part will deal with how to display the forms for registration and login. Within Castle’s MVC stack, ActiveRecord typically represents the model, Monorail controllers deal with the workflow (and controll the session), and View engines such as NVelocity deal with the user interface.

ActiveRecord is probably not the best choice for the model in complex applications, because it effectively has two very distinct responsibilities: domain logic and storage. When building more complex systems, it makes a lot of sense to use a different pattern for storage, such as the Repository pattern, and have a separate layer of domain logic built around plain CLR objects. Castle also integrates nicely with NHibernate directly (and with iBatis ORM framework, if you do not want to use NHibernate), which allows us to implement a different storage pattern. However, that requires somewhat more configuration and code, and we’ll leave that for later articles in this series. For now, let’s keep it simple.

Completing the domain for User Registration

Our User record class already provides a way for new users to register, simply by creating and saving a new user object. But we have to add the logic for verifying login details. Such domain logic methods that do not operate on a particular User object, but relate to the whole concept of Users, are typically implemented as static methods in the record class. We can use FindAllByProperty (provided by the ActiveRecordValidationBase which the User class extends) to get a list of User objects matching a particular property — in this case we can look for a matching username. Then we verify that the password is correct and return the appropriate user object. If the passwords do not match, or there is no user with requested username, we return null. In a real system, passwords should be kept encrypted and we would probably have to verify a password hash against the stored value, but let’s keep it simple in this tutorial. Here is the method to add to the User class:

public static User Login(string username, string password) 
{ 
    User[] user = User.FindAllByProperty("Username", username); 
    if (user.Length == 0) return null; 
    if (user[0].password.Equals(password)) return user[0]; 
    return null; 
} 

Let’s add a few unit tests to verify that this method works correctly to the UserTests class:

[Test] 
public void TestLoginCorrect() 
{ 
    User mike = new User("Mike Smith", "mike", "mpass123", "mike@mike.com"); 
    mike.Save(); 
    User tom = new User("Tom Smith", "tom", "tom123", "tom@tom.com"); 
    tom.Save(); 
    Flush(); 
    User loggedIn = User.Login("mike", "mpass123"); 
    Assert.AreEqual(mike, loggedIn); 
  
} 
[Test] 
public void TestLoginWrongPw() 
{ 
    User mike = new User("Mike Smith", "mike", "mpass123", "mike@mike.com"); 
    mike.Save(); 
    Flush(); 
    User loggedIn = User.Login("mike", "mpass12"); 
    Assert.IsNull(loggedIn); 
} 
[Test] 
public void TestLoginWrongUsername() 
{ 
    User mike = new User("Mike Smith", "mike", "mpass123", "mike@mike.com"); 
    mike.Save(); 
    Flush(); 
    User loggedIn = User.Login("ke", "mpass12"); 
    Assert.IsNull(loggedIn); 
} 

Setting up the projects

To start building our web site, we now add two new projects to the solution. One project is a plain C# class library that will contain our controllers and other web application code — let’s call it EvilLink.Controllers. The second project is the actual web site: configuration and layout files — effectively the View part of the MVC stack. Let’s call the second project EvilLink.WebSite. If you are working with the commercial version of Visual Studio, you can use the Web application template to build the second project. If you are working with the Express version, create it as a Class Library project, then go to project build properties (right-click on the project, select properties, then click on the Build tab) and change the output path to bin\ (it’s probably bin\Release or bin\Debug by default). The reason for this is that IIS automatically loads DLLs from the bin folder of Web applications, so we want Visual Studio to put all dependencies there. Add the following references to the controllers project: Castle.ActiveRecord.dll, Castle.Core.dll, Castle.Monorail.Framework.dll, System.Web from GAC and EvilLink.Database project. Add the following references to the web site project: Castle.ActiveRecord.dll, Castle.Components.Validator.dll, Castle.Core.dll, Castle.DynamicProxy.dll, Castle.Monorail.Framework.dll, Castle.Monorail.Framework.Views.Nvelocity.dll, log4net.dll, NHibernate.dll, NVelocity.dll (the last three are in the dependencies zip file of the Castle distribution), EvilLink.Controllers and EvilLink.Database projects. If you are using the Express version of Visual Studio, configure a Web site in IIS to point to the root of the EvilLink.WebSite project.

Creating the web application

We need to initialise ActiveRecord properly when the Web application starts. To do that, we’ll replace the default Web application with our own. Instead of loading ActiveRecord configuration from a separate file, let’s keep it in web.config with all the other web application parameters. Instead of using the XmlConfigurationSource, like in the unit tests, we can specify that ActiveRecord should load the configuration from the main configuration section with ActiveRecordSectionHandler.Instance. So add this class to the Controllers project:

using System.Reflection; 
using Castle.ActiveRecord.Framework.Config; 
using Castle.ActiveRecord; 
  
namespace EvilLink.Controllers 
{ 
    public class EvilLinkWebApp : System.Web.HttpApplication 
    { 
        public void Application_OnStart() 
        { 
                ActiveRecordStarter.Initialize( 
                    Assembly.GetAssembly(typeof(EvilLink.Database.User)), 
                ActiveRecordSectionHandler.Instance); 
        } 
    } 
} 

Create a file called global.asax in the EvilLink.WebSite project root, and set the Web application class:

< %@ Application Inherits="EvilLink.Controllers.EvilLinkWebApp, 
EvilLink.Controllers" %>

Now let’s create the web.config file in the EvilLink.WebSite project root. To configure Monorail, we add a MonoRailSectionHandler section, specify the assembly that contains controller classes (in this case EvilLink.Controllers) and define the view engine (for this example we use NVelocity). The controller code is decoupled from the view code — controllers are not concerned with the actual display of results. NVelocity view engine uses templates written in the Velocity language. There are other view engines that you can use, including the WebForms view engine (although I honestly don’t know anyone who uses that). To make Monorail work, we also need to load the Monorail HTTP module and then route some requests to Monorail. People often use .rails as the extension for Monorail, but I like to use one of the standard .NET extensions to keep things consistent with the .NET environment and make the Web server configuration simple — in this example we map all requests for .ashx pages to Monorail. If you want to use a non-standard extension (like .rails), don’t forget to to assign that content type to ASP.NET in IIS configuration (remember to uncheck the Verify that file exists option for that extension). To configure ActiveRecord, we just copy the configuration from unit tests into an ActiveRecordSectionHandler section. The only thing that should be changed is the isWeb attribute — in this case, set it to true. Here is the full configuration file:

<?xml version="1.0" encoding="utf-8" ?> 
<configuration> 
  <configSections> 
    <section name="monorail"  
      type="Castle.MonoRail.Framework.Configuration.MonoRailSectionHandler,  
            Castle.MonoRail.Framework" /> 
     <section name="activerecord" 
      type="Castle.ActiveRecord.Framework.Config.ActiveRecordSectionHandler,  
            Castle.ActiveRecord" /> 
   </configSections> 
   <monorail> 
      <controllers> 
        <assembly>EvilLink.Controllers</assembly> 
      </controllers> 
      <viewEngines viewPathRoot="Views"> 
        <add type="Castle.MonoRail.Framework.Views.NVelocity.NVelocityViewEngine,  
                   Castle.MonoRail.Framework.Views.NVelocity" /> 
      </viewEngines> 
    </monorail> 
    <activerecord isWeb="true" pluralizeTableNames="true"> 
      <config> 
        <add key="hibernate.connection.driver_class"  
             value="NHibernate.Driver.SqlClientDriver" /> 
        <add key="hibernate.dialect"                  
             value="NHibernate.Dialect.MsSql2005Dialect" /> 
        <add key="hibernate.connection.provider"      
             value="NHibernate.Connection.DriverConnectionProvider" /> 
        <add key="hibernate.connection.connection_string"  
             value="Data Source=MALI\SQLEXPRESS;Initial Catalog=castletest; 
                    user id=castletest;password=castletest" /> 
     </activerecord> 
  <system.web> 
    <httpHandlers> 
      <add verb="*" path="*.ashx"  
       type="Castle.MonoRail.Framework.MonoRailHttpHandlerFactory,  
             Castle.MonoRail.Framework" /> 
     </httpHandlers> 
   <httpModules> 
      <add name="monorail"  
        type="Castle.MonoRail.Framework.EngineContextModule,  
              Castle.MonoRail.Framework" /> 
    </httpModules> 
  </system.web> 
</configuration> 

Hello World from Monorail

Let’s start with a really simple example, just to verify that everything is properly set up. Three basic Monorail concepts that we’ll use in this exercise are:

  • View: a particular visual display of some data. It does not contain any workflow or business logic — it is only concerned with how to render HTML from the provided information.
  • Controller: a .NET class handling the UI workflow for a set of related operations. This class handles all web requests and instructs Monorail which view to use to display the results, but not how to display them. This loosely corresponds to a web folder in a non-MVC environment.
  • Action: a method of the controller used to coordinate a single operation. This loosely corresponds to a single web page in a non-MVC environment.

Now we create a controller that will display a form for entering a message, and after you type in something, print that message. Add this class to the EvilLink.Controllers project:

using System; 
using Castle.MonoRail.Framework; 
  
namespace EvilLink.Controllers 
{ 
    public class HelloWorldController:SmartDispatcherController 
    { 
        public void Index() 
        { 
        } 
        public void ShowMessage(String message) 
        { 
            PropertyBag["message"] = message; 
        } 
    } 
} 

This controller extends the SmartDispatcherController. Most of your controllers will do the same. SmartDispatcherController will append the word Controller to the request folder name and look for the class, then find a method based on the file path of the URL, without the file extension. In this case, if your local web site is running on http://localhost, then the request for http://localhost/helloworld/index.ashx would call the Index method of the HelloWorldController class. The request for http://localhost/helloworld/showmessage.ashx would call the ShowMessage method. GET/POST request parameters are automatically mapped to method parameters, based on name matching. SmartDispatcherController promotes the use of convention over configuration, so its programming model is very light-weight. Notice that there is no HTTP processing code in this class — Monorail takes care of all the plumbing. This is a simple optimization of work required to process a web request, but it makes the code much more clear. Considering that an average web site can have anywhere from a few dozen to a few hundred similar actions, this optimisation saves a lot of time and effort. It takes less code to build the web site, and less code means easier maintenance and less chance for errors.

The job of a controller in Monorail is to coordinate requests to the model classes to complete the workflow, and then prepare information that a view will use to render HTML. The data that should be available to the view is just stored in the PropertyBag dictionary. In this example, we use the message key in the PropertyBag dictionary to pass on the message that a user typed in.

Now, go to the EvilLink.WebSite project, and create a views folder. Then create a HelloWorld subfolder within that folder. This is where Monorail will look for the view files for the HelloWorld controller. In this exercise, we use the NVelocity template engine for views, and NVelocity files typically have the .vm extension. So create a text file called index.vm inside the HelloWorld folder and paste this content:

Hello world from Monorail! Type in a message:
<form method="post" action="showmessage.ashx">
<input type="text" name="message" />
<input type="submit" value="Show" />
</form>

This is just a plain HTML file, with a form that will call our ShowMessage action. Notice that the form has a text field called message — this field will be mapped to the ShowMessage method argument of the same name. Now create showmessage.vm in the same folder, with the following content:

You typed in: ${message}

Velocity uses a simple template language to access objects from the PropertyBag dictionary. In this case, we just enclose the object name into ${} and it gets printed out. You can use this syntax to access object properties or call object methods as well.

Rebuild the solution (so that Visual Studio copies all required DLLs to the WebSite bin folder), and open your browser. Navigate to http://localhost/helloworld/index.ashx and you should see the web form:

Type in something, then click on Show. You should see the confirmation page.

That’s it! Monorail works. Take a breath and let’s move on to more complex stuff.

Layouts

In the HelloWorld example, our view specified the entire HTML response contents. In most web applications, such an approach would not be practical. Common content such as menus, headers and footers should be maintained at a single place. Common CSS styles and JavaScript files need to be included in every page. This functionality is provided in Monorail with Layouts. A Monorail Layout is a skeleton of a set of related web pages, similar to ASP.NET master pages. So, let’s create a layout for our web site. Add a layouts folder under the views folder (that’s where Monorail looks for layouts), and then create a file called default.vm with this content:

<html> 
<head><title>Evil Link v 1.0</title></head> 
<link rel="stylesheet" href="$siteRoot/views/layouts/default.css" /> 
<body> 
      #if($message) 
      <div class="message"> 
            $message 
      </div> 
      #end 
      #if($error) 
      <div class="error"> 
            $error 
      </div> 
      #end 
      $childContent 
</body> 
</html> 

There are several things to notice in this example:

  • we can use a shorter notation for accessing PropertyBag contents if they are just a single word — just prefix the word with a dollar sign.
  • $siteRoot is a global template variable that allows us to load resources using relative paths.
  • we can use #if #end blocks to check whether a PropertyBag message is defined.
  • $childContent is a special variable for layouts that defines where the actual view content will go

This particular layout will display errors or messages passed by the controller for all actions, and it will also load the CSS rules specified in default.css in the same folder. Here is what I’ve used for the CSS rules (feel free to add your own):

table { border:1px solid black;}
.error { border:1px solid red; width:300px; 
  padding:5px 5px 5px 5px; text-align:center;}
.message { border:1px solid black; width:300px; 
  padding:5px 5px 5px 5px; text-align:center;}

To apply a layout to a controller, just add a Layout attribute to the class. For example, add [Layout(“default”)] to the HelloWorldController to use the “default” layout.

Working with session objects

We need to know whether a user is logged in or not logged in in our application. For that, I typically use the HTTP session object (Yes, .NET also has the Principal object that might be better for this purpose, but we’ll use the session to show how to work with some other integrations later as well). In the controller class, you can use the Session property to access the HTTP session, but I do not like such uncontrolled global access to session variables (I wrote about that last week). So instead of having controllers manipulate the session directly, let’s encapsulate session access into a class and then have controllers use that instead. Because we do not have access to the Monorail context in a standalone class, we can use System.Web.HttpContext.Current to get to the current HTTP request context. Here is the session context wrapper class:

using System; 
using System.Web; 
using EvilLink.Database; 
  
namespace EvilLink.Controllers 
{   
    public class UserContext 
    { 
        public int? CurrentUserId 
        { 
            get { return System.Web.HttpContext. 
                    Current.Session["currentUserId"] as int?; } 
            set { System.Web.HttpContext. 
                Current.Session["currentUserId"] = value; } 
        } 
        public User CurrentUser 
        { 
            get { return User.Find(CurrentUserId); } 
        } 
    } 
} 

The User Operation Controller

For this example, we want to display the login form with a link for registration to users that are not yet logged in. They can then log in (let’s just display a confirmation message after they log in for now) or follow the registration link to get to the registration form, where they will be able to fill in their details and register. After the registration, let’s also just display a confirmation message for now. On the end, let’s also allow people to log out. So we have five actions for our controller (and five corresponding methods):

  • Displaying login form (let’s call this Index)
  • Processing login details (let’s call this Login)
  • Logging out (let’s call this Logout)
  • Displaying the registration form (let’s call this Register)
  • Processing registration details (let’s call this SubmitRegistration)

Chaining actions and the Flash

In the HelloWorld example, the view template file name was taken from the method name. This is one more example of promoting convention over configuration. However, that is not always practical. Some actions will have to display the same view. For example, if a user tries to register with invalid details or the requested username is already taken, we should display the registration form again. That form is already displayed by the Register view, and there is no need to duplicate the code.

Monorail controller does not need to execute the same view every time. By default, the view name is taken from the action name. The view name can also be explicitly set using the RenderView method. You can also call one action method from another, but the default view will still be the same as the first method called. A controller action can also issue a redirect command, that sends the user to another action (or another controller). The redirect will be executed using the HTTP redirection method. So, for example, if the SubmitRegistration method calls the Register method directly, than those two would be executed as part of the same HTTP request (and the SubmitRegistration view would be rendered on the end, unless explicitly specified otherwise). If the SubmitRegistration method calls RedirectToAction (“register”) then the client would get a HTTP redirect message, the browser would make another request for the Register action, and the Register method would be called (rendering the Register view unless specified differently). Here is a short summary of what happens in several different cases when we chain parts of the Redirect and Index actions:

Call within the SubmitRegistration method Number of Http requests Register() body executed Rendered view
Register() One Yes SubmitRegistration
RenderView(“register”) One No Register
RedirectToAction(“register”) two Yes Register

If the Register method is called directly, or if the register view is rendered in the SubmitRegistration method, the PropertyBag context set by the SubmitRegistration method would available to the final view as well. In the third case, where we have two HTTP requests, it would be lost. If we wanted to make some data available to the view for the next request, we could use the HTTP session. There is also a much simpler way to do it if we only want to keep it for the next request, not for any subsequents requests after that. The Monorail Flash object is used to store information between requests in a sequence. For example, we can set an error message and redirect to a different action like this:

Flash["error"] = "Username or password incorrect, please try again"; 
RedirectToAction("index");

When the Index method gets called in the subsequent request, “Username or password incorrect, please try again” will be available directly to the view under the name “error”. The index page could display the error message and then the login form. This is a good way to provide feedback to users without introducing additional workflow. We can also access the Flash contents using a standard $Flash variable (eg $Flash.message would print out the “message” key from the Flash).

Armed with all this knowledge, we can now write the user operation controller. Add this class to the EvilLink.Controllers project:

using System; 
using Castle.MonoRail.Framework; 
using EvilLink.Database; 
  
namespace EvilLink.Controllers 
{ 
    [Layout("default")] 
    public class UserOpsController : SmartDispatcherController 
    { 
        private UserContext context=new UserContext();  
        public void Index() 
        { 
            if (context.CurrentUserId != null) 
            { 
                PropertyBag["error"] 
                 = "You are already logged in. Please log out"; 
            } 
        } 
        public void Login(String username, String password) 
        { 
            User u = User.Login(username, password); 
            if (u != null) 
            { 
                context.CurrentUserId = u.Id; 
                PropertyBag["user"] = context.CurrentUser; 
                RenderView("postlogin"); 
            } 
            else 
            { 
                Flash["error"] = "Username or password incorrect,"+
                 " please try again"; 
                RedirectToAction("index"); 
            } 
        } 
        public void Logout() 
        { 
            context.CurrentUserId = null; 
            RenderView("index"); 
        } 
        public void Register() 
        { 
            PropertyBag["user"] = new User(); 
        } 
        public void SubmitRegistration([DataBind("user")] User user) 
        { 
            if (!user.IsValid()) 
            { 
                PropertyBag["error"] = String.Join(", ", 
                    user.ValidationErrorMessages); 
                PropertyBag["user"] = user; 
                RenderView("register"); 
                return; 
            } 
            try 
            { 
                user.CreateAndFlush(); 
                Flash["message"] = "You have registered successfully. "+
                 "You can now log in."; 
                RedirectToAction("index"); 
            } 
            catch (Exception e) 
            { 
                PropertyBag["error"] = e.Message; 
                Logger.Error("error during registration", e); 
                PropertyBag["user"] = user; 
                RenderView("register"); 
            } 
        } 
    } 
}

Before we continue with the views, notice that we are using the Flash to pass error messages or notifications before redirecting to other actions, and that we are directly setting the PropertyBag error and message keys when the view is getting rendered in the same request. Also, notice how we use the logging facility: the Logger property is available to you if you extend SmartDispatcherController, and it will use Monorail’s logging (you can set this up to be either Log4Net or NLog), but the code stays the same.

A very important line to look at is the SubmitRegistration method header. Monorail can load complete objects for you, not just simple properties. To do that, you have to add the DataBind attribute to the argument. Unfortunately, in this case Monorail does not use convention over configuration, so you will have to specify the parameter name as well. The value from the DataBind attribute is used like a prefix to map request parameters to object properties. In this case, the request parameter user_username would be mapped to the Username property of the method argument. Because we map the request arguments directly to a Monorail class, we can check whether the user details are valid and try to create a new user in only a few lines of code.

User operation views

The UserOpsController uses three views. Index is used to display the login form. Postlogin is used to greet the user after logging in, and register displays the registration form. Notice that there is no Postlogin method, and that the SubmitRegistration method does not have a view named after it.

The index view is the simplest. It should just display the login form and a link for registration. Remember that any errors or notification messages will be handled by the layout directly. Here is the index.vm file:

Please log in
<br/>
<table>
<form method="post" action="login.ashx">
<tr><td>Username</td><td><input type="text" name="username"/></td></tr>
<tr><td>Password</td><td><input type="password" name="password" /></td></tr>
<tr><td></td><td><input type="submit" value="Log In" /></td></tr>
</form>
</table>
<br/>
if you do not have an account, <a href="register.ashx">register</a> now

As mentioned before, you can use more complex objects directly in the PropertyBag. After a user successfully logs in, we store the whole user object in the PropertyBag to give our view the choice to display anything it sees fit. Remember, the controller should only coordinate actions and make the appropriate data available to the view, not decide what gets displayed or how. Here is postlogin.vm, that in this case greets the user by his name:

You are logged in as ${user.Name}. <a href="logout.ashx">Log out</a>.

The register.vm view introduces a new concept. We want to reduce code duplication, and we want to display the same form to the user when they first try to register, but also when they tried to register with invalid data. Monorail uses something similar to Rails helpers here, and allows us to quickly build such forms. Here is what register.vm looks like:

<h1>Register as a new user</h1>
<form method="post" action="submitregistration.ashx">
<table>
<tr><td>Username</td><td>$FormHelper.TextField("user.username")</td></tr>
<tr><td>Password</td><td>$FormHelper.PasswordField("user.password")</td></tr>
<tr><td>E-mail</td><td>$FormHelper.TextField("user.email")</td></tr>
<tr><td>Name</td><td>$FormHelper.TextField("user.name")</td></tr>
<tr><td></td><td><input type="submit" name="Register" /></td></tr>
</table>
</form>

Instead of typing in field names directly and checking whether the $user object has appropriate fields, we can use the $FormHelper variable to quickly script the user form. See the FormHelper page in Castle documentation for more information on this tool.

The finishing touch

We are almost done. Let’s make the browsing experience a bit better by presenting the users with a login form when they browse to the web site homepage. There are two ways to do this: either map all IIS requests to .NET and then set up Monorail to handle requests without an extension. Another, that is in my mind simpler, is to create an ASP.NET default page that redirects to the appropriate controller action. So let’s add this as the Default.aspx page to the web site:

<%@ Page Language="C#" %>
<%
	Response.Redirect("/userops/index.ashx");
%>

You can now recompile the project, browse to the homepage of your localhost site, and you should see the login form. Try to register, log in and out. Note that you can change the layout and view files without recompiling or restarting the web site. You only need to recompile the projects after modifying the controller code.

You can download the source code for this example from http://gojko.net/resources/EvilLink_i2.zip

Key stuff to remember

  • The Model-View-Controller pattern requires a clear separation of responsibilities between the domain business logic (model), workflow (controller) and the actual user interface (view).
  • ActiveRecord must be properly initialised in the Application OnLoad event. The easiest way to do that is to extend the HTTPApplication object and just load ActiveRecord configuration from web.config.
  • SmartDispatcherController maps web folders to class names, web file names to methods and web parameters to request arguments. It works with both simple and complex objects, but complex objects require the DataBind attribute as well.
  • PropertyBag contents are passed to the view, and the view should decide what to actually display.
  • Monorail layouts are templates that specify common parts of a set of pages. Use the Layout attribute on the controller to specify which layout template should be used.
  • Use the Flash object to transfer messages when redirecting to a different action.
  • $FormHelper allows us to quickly script HTML forms.
  • ActiveRecord is probably not the best choice for the model for complex applications, because it is a mix of domain logic and storage. Monorail integration features, however, make it an interesting choice for simpler applications.

Next exercise

In the next part of this tutorial, we build web pages to manage links, which allows us to see how ActiveRecord and Monorail work together effectively. We also introduce Monorail rescues and page components. I will post the next part on this web site early next week. You can subscribe to RSS updates to get a notification when the next part is online. I will be discussing this demo application and several upcoming articles of this series this week in London on a workshop on Agile Web development with Castle. After the talk, we will meet up with the ALT.NET enthusiasts for beers.

Image Credits: Igor Kasalovic

I'm Gojko Adzic, author of Impact Mapping and Specification by Example. My latest book is Fifty Quick Ideas to Improve Your User Stories. To learn about discounts on my books, conferences and workshops, sign up for Impact or follow me on Twitter. Join me at these conferences and workshops:

Specification by Example Workshops

Product Owner Survival Camp

Conference talks and workshops

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>