Click here to Skip to main content
15,878,809 members
Articles / Web Development / ASP.NET

Monorail Hands-On

Rate me:
Please Sign up or sign in to vote.
4.78/5 (15 votes)
19 Feb 2008CPOL9 min read 71.1K   1.1K   40   8
In this article, I will present a sample application using the Monorail framework and provide the basic concepts of the design pattern known as MVC.

Northwind_Monorail.JPG

Introduction

I've decided to write this article for two reasons: first, my enthusiasm with the Monorail framework, and second, the .NET community has displayed a growing interest on MVC as an alternative to traditional WebForms applications, not only because of Monorail, but also due to the rise of the new ASP.NET MVC framework.

The MVC Pattern

MVC is the acronym for Model-View-Controller, a design pattern that enforces the separation of concerns on the presentation layer. Note that MVC is in no way limited to web applications; it might also be applied to other platforms that expose a presentation layer, e.g., a desktop environment.

MVC enables the decoupling of data access logic (model) from user interface logic (view), and establishing the controller as an intermediate component.

Model: Represents the domain, that is, the entities that encapsulate raw data plus the domain logic. Note that the MVC pattern doesn't mention how the data persistence should be implemented. Usually, it is encapsulated by the Model component.

View: This comprises the user interface components. They know how to present data, and expose graphic elements such as buttons, lists, and text boxes, that allow user interaction.

Controller: The controller is the component that orders the rendering of the view, responds to events of the user interface, and invokes changes in the model.

Image 2

Although some authors conclude that WebForms implement the MVC pattern, it must be said that in WebForms, the controller rule is shared between the ASPX file, the code-behind, and the web controls, which goes against the separation of concerns and the single responsibility principle.

The Monorail Framework

Monorail, developed by Castle Project since 2003, is an Open Source MVC framework for .NET web applications, inspired on the Ruby on Rails Action Pack. The Monorail flow could be described in a few lines, but since a picture is worth 10,000 words...

Image 3

Note that Monorail has an implementation of IHttpHandler, so it still takes advantage of the ASP.NET infrastructure (e.g.: session management, events, and security).

Having said that, we can enumerate some advantages of Monorail over traditional WebForm applications:

  • Unlike WebForms, Monorail doesn't have a complex page life cycle.
  • In Monorail, the separation of concerns principle is enforced. When you develop your Monorail app, you naturally think of it as a structure of models, views, and controllers. In WebForms, on the other hand, the designer hides the relationship of these structures from the developer, often resulting in a bad practice of mixing view code and controller code. Monorail doesn't have a designer, so you must write your views manually. It might be considered a bad thing, that's true, but you have total control over how your HTML is written. In WebForms, on the other hand, the designer often messes up your HTML code, and even introduces unneeded code.
  • With Monorail, one doesn't have to be concerned with view state. Although HTTP is stateless by nature, with WebForms, Microsoft has introduced the idea of a view state, maintaining the view data through a page's life cycle. This is good, but adds too much complexity to the development, and decreases overall performance, and introduces security issues.
  • Monorail allows unit testing over controllers, due to the decoupled design. On the other hand, WebForms design makes it difficult to perform unit testing on the code-behind.

Monorail Hands-On: The Northwind Traders Sample Application

The goal of the Northwind Traders application is to provide simple CRUD functionalities for Products and Suppliers of the company. That is, we must be able to create, retrieve, update, and delete products and suppliers of Northwind Traders through our Monorail application.

The structure of the Northwind Traders project follows Monorail standards: we have a Monorail project, much like a traditional WebForms project, and it has separate folders for Models, Views, and Controllers.

Northwind_Monorail_Project.JPG

The Models folder contains the entities Product and Supplier, which are persisted to the SQL Server database through the ActiveRecord framework.

The Views folder contains the .vm files, which are basically NVelocity template files used by Monorail to generate the final HTML code for the view.

The Controllers folder contains the controller classes, ProductController and SupplierController. Each controller has the methods representing the actions emitted against the controller to perform the CRUD functionalities on the entities.

URL Routing

Unlike WebForms websites, where each URL is redirected to a physical file in the website, in Monorail, the URL is written to be re-routed not to a file, but to a specific action in a specific controller, following this format:

http://website/MonorailApplication/controller/action.rails

Example:

http://localhost/monorail/product/masterdetail.rails

The result of this request is the rendering of the following page:

Northwind_Monorail.JPG

Let's try to understand how we got there from our URL:

http://localhost/monorail[1]/product[2]/masterdetail[3].rails[4]
  • [1] monorail is our website.
  • [2] product tells the Monorail framework to instantiate the ProductController class.
  • [3] masterdetail tells the Monorail framework to invoke the MasterDetail method in the ProductController class.
  • [4] .rails is already configured in IIS as an extension associated with the ASP.NET ISAPI. Once ASP.NET is called, it investigates our web.config file, where there is a configuration (httphandlers tag) that tells ASP.NET to redirect every ".rails" request to the Monorail Framework.

The Domain Model

I could have added more entities to our model, but for the sake of simplicity, I decided to keep only the Product and Supplier. Each entity class is inherited from the ActiveRecordBase class, which automatically provides Refresh, Delete, Update, Create, and Save functionalities.

The code below represents the Product class:

C#
using System;
using Castle.ActiveRecord;

namespace Northwind_Monorail.Models
{
    [ActiveRecord("Products")]
    public class Product : ActiveRecordBase<Product>
    {
        private int id;
        private String name;
        private decimal price;
        private Supplier supplier;

        [PrimaryKey("ProductID")]
        public int Id
        {
            get { return id; }
            set { id = value; }
        }

        [Property("ProductName")]
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        [Property("UnitPrice")]
        public decimal Price
        {
            get { return price; }
            set { price = value; }
        }

        [BelongsTo("SupplierId")]
        public Supplier Supplier
        {
            get { return supplier; }
            set { supplier = value; }
        }
        
        public new static Product[] FindAll()
        {
            Product[] products = (Product[])FindAll(typeof(Product));
            return products;
        }

        public static Product FindById(int id)
        {
            return (Product) FindByPrimaryKey(typeof(Product), id);
        }
    }
}

Just a few notes on the Product class:

  • Note that the Product class is decorated by the [ActiveRecord("Products")] attribute, which means that the Product class is mapped to the Products table in the Northwind database.
  • The Id property is decorated by [PrimaryKey("ProductID")], which means that it is mapped to Products' primary key column (ProductID) in the Northwind database.
  • Each non-key property is mapped to the table through the [Property()] attribute.
  • The Product class has a reference to the Supplier class. This is done by the Supplier property, which is of Supplier type. The attribute [BelongsTo("SupplierId")] informs which property of the Supplier (SupplierID) is the foreign key property.

The Views

The view files in our project have the .vm extension, which means they will be processed by NVelocity. NVelocity is a template engine; that is, NVelocity files will be injected with information from controllers in order to form the final HTML code. You can use other view engines, like Brails or even .aspx (although this last one is not advisable, since you wouldn't be able to use Web Controls). But here, we will deal with the NVelocity view engine only.

As an example, the following code snippet represents the index.vm file:

XML
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
        "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
  <head>
    $AjaxHelper.GetJavascriptFunctions()
    $ScriptaculousHelper.GetJavascriptFunctions()

    <script language="javascript" type="text/javascript">
.
.
.
    <div id="headerwrap">
      <div id="header">
        #parse("menu\\headermenu.vm")
      </div>
      <div id="middlewrap">
        <div id="middle">
          <div id="sidebar">
            #parse("menu\\shortcut.vm")
          </div>
          <div id="content">
            <div id="statusbar"></div>
            <div id="details">#parse("product\\edit.vm")<div>
            <div id="search">#parse("product\\search.vm")</div>
            <div id="list">#parse("product\\list.vm")</div>
          </div>
        </div>
      </div>
    </div>
    <div id="footerwrap">
      <div id="footer">
      </div>
    </div>
  </body>
<html>

Notice the #parse directives in the view above. The role of the #parse directive is to render the content of another view within the view, so that you can keep clean, small, and cohesive views for your website.

The following picture explains how partial views are rendered by the main product view (index view):

views.JPG

The Controllers

The code below represents the ProductController class:

C#
using System;
using System.Collections;
using System.Collections.Generic;
using Castle.MonoRail.Framework;
using Castle.MonoRail.Framework.Helpers;
using Castle.Monorail.JSONSupport;
using Newtonsoft.Json;
using NHibernate.Expression;
using Northwind_Monorail.Models;
using Northwind_Monorail.Queries;

namespace Northwind_Monorail.Controllers
{
    [Layout("default"), Rescue("generalerror"), 
      Helper(typeof(JSONHelper), "Json")]
    public class ProductController : SmartDispatcherController
    {
        .
        .
        public void Index([DataBind("product", 
                           Validate = true)] Product product)
        {
            PropertyBag["products"] = new Product();
            string productName = "";
            int supplierId = 0;
            PropertyBag["products"] = 
              PaginationHelper.CreatePagination(this, 
                 GetProducts(productName, supplierId), 10);
            PropertyBag["suppliers"] = GetSuppliersWithABlankItem();

            List<Shortcut> shortcuts = new List<Shortcut>();
            Shortcut scProduct = new Shortcut();
            scProduct.Text = "Products";
            scProduct.Image = "\\Images\\products.gif";
            scProduct.Url = "\\product\\Index.rails";
            scProduct.DetailUrl = "\\product\\edit.rails";
            scProduct.SearchUrl = "\\product\\search.rails";
            scProduct.ListUrl = "\\product\\list.rails";
            scProduct.Tooltip = "Manage Products";
            Shortcut scSupplier = new Shortcut();
            scSupplier.Text = "Suppliers";
            scSupplier.Image = "\\Images\\suppliers.gif";
            scSupplier.Url = "\\supplier\\Index.rails";
            scSupplier.DetailUrl = "\\supplier\\edit.rails";
            scSupplier.SearchUrl = "\\supplier\\search.rails";
            scSupplier.ListUrl = "\\supplier\\list.rails";
            scSupplier.Tooltip = "Suppliers";

            shortcuts.Add(scProduct);
            shortcuts.Add(scSupplier);

            PropertyBag["shortcuts"] = shortcuts;
        }
        .
        .
        .

Suppose you request the following URL to the website:

http://localhost/monorail/product/index.rails

The following events will take place:

  1. The Monorail framework will invoke the action (i.e., the index() method) of an instance of the ProductController class.
  2. The index method will retrieve data from model (both product and supplier data) and store it in a PropertyBag list.
  3. After the execution of the product controller, the Monorail framework will render the view corresponding to the action (index.vm).
  4. The view will populate the dropdown list with supplier information, and the grid with the product information.

Downloading and Installing the Sample Application

Installing the Northwind Database

Since the sample application depends on the Northwind database:

  • You must have a local SQL Server 2000 or 2005, or have access to a remote one.
  • Make sure your server has a Northwind sample database installed. If not, please download it from the Microsoft Download Center and restore it on your server.

IIS Configuration

A request to a Monorail application is identified by IIS by the URL extension. Usually, that extension is .rails (like in our sample), but you can use other extensions, if they aren't in use yet. When users call a URL of your Monorail web application, the Internet Information Server must first pass that information to the ASP.NET ISAPI, so that the ASP.NET framework can read your app's web.config, where there is an explicit instruction to hand over control to the Monorail framework whenever the .rails extension is found:

XML
<httpHandlers>
    <add verb="*" path="*.rails"
    type="Castle.MonoRail.Framework.MonoRailHttpHandlerFactory,
    Castle.MonoRail.Framework"/>
.
.
.
</httpHandlers>
Httphandler tag in Web.config file

But, in order for the .rails extension to be processed, it must first be associated with the ASP.NET ISAPI. You can do it by configuring IIS as follows:

  1. Open the IIS Management Console.
  2. Right-click the Default Web Site item and choose Properties.
  3. Select the Home Directory tab.
  4. Click Configurations.
  5. Image 7

    Image 8

  6. Click Add.
  7. Select the ISAPI DLL. You can copy-and-paste the complete DLL file name from another extension, such as .aspx. In most systems, it will be something like C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\aspnet_isapi.dll (for .NET 1.1) or C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll (for .NET 2.0).
  8. Fill in the extension (for example, .rails) as the file extension (make sure you do not omit the leading dot).
  9. Uncheck the Check file exists check box.

Image 9

For more detailed information, see: Installing Monorail.

Note: We had to perform this configuration above in the IIS because in this sample, we are using the .rails extension. However, as our reader mentifex pointed out in his comments, you can make your life easier by changing your web.config file to use the .ashx extension instead of .rails. ASHX (ASP.NET Web Handler File) is an ASP.NET native extension, and you don't have to configure manually its mapping on the IIS, since it is already mapped to the ASP.NET ISAPI. Besides, in cases when you don't have permission to change your IIS configuration, .ashx is your only choice. Just change your web.config to map .ashx files to the Monorail framework, and you're done.

Downloading the Sample Application

Next, you download the Northwind Traders application attached to this article to a local folder:

Northwind_Folder.JPG

Then, you create a virtual directory for the application:

Northwind_Site.JPG

Important: Make sure your site was created for ASP.NET 2.0.

That's it!

Downloading the Source Code

In order to open the Northwind Monorail project attached to this article, you should first install Castle Project Release Candidate 3 MSI.

History

  • 2008-02-19: Created.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Instructor / Trainer Alura Cursos Online
Brazil Brazil

Comments and Discussions

 
QuestionDefault view and nested controller actions Pin
Thomas Eyde25-Feb-08 13:54
Thomas Eyde25-Feb-08 13:54 
GeneralRe: Default view and nested controller actions Pin
etal25-Feb-08 21:51
etal25-Feb-08 21:51 
GeneralRe: Default view and nested controller actions Pin
Thomas Eyde25-Feb-08 23:29
Thomas Eyde25-Feb-08 23:29 
GeneralRe: Default view and nested controller actions Pin
etal26-Feb-08 0:46
etal26-Feb-08 0:46 
Generalgreat Pin
User 17912925-Feb-08 12:28
professionalUser 17912925-Feb-08 12:28 
GeneralRe: great Pin
Marcelo Ricardo de Oliveira26-Feb-08 9:28
Marcelo Ricardo de Oliveira26-Feb-08 9:28 
Generalnice to see monorail and a suggestion Pin
cyonite19-Feb-08 2:10
cyonite19-Feb-08 2:10 
GeneralRe: nice to see monorail and a suggestion Pin
Marcelo Ricardo de Oliveira19-Feb-08 5:26
Marcelo Ricardo de Oliveira19-Feb-08 5:26 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.