Getting started

This "getting started" is based on VS2008. All tutorials are included in the source distribution

Simple Monorail tutorial

First we create a new ASP .NET Web Application solution and name it GettingStartedSample.

New project in VS2008 of the 'ASP.NET Web Application' type

We now add a reference to the 'org.SharpTiles.dll'.

Add Reference > Browse > org.SharpTiles.dll

Since it's not required to have Monorail installed for this "getting started", references to the following Castle DLL's (which can be found on http://www.castleproject.org/castle/download.html and in the SharpTiles distribution) are also needed:

  • Castle.Monorail.Framework.dll
  • Castle.Components.Binder.dll
  • Castle.Components.Common.EmailSender.dll
  • Castle.Components.Validator.dll
  • Castle.Core.dll

The fourth step is configuring the web.config file. A Monorail configuration section, a http handler and a http module need to be registered:

<configuration>
  <configSections>
    <section name="monorail" type="Castle.MonoRail.Framework.Configuration.MonoRailSectionHandler, Castle.MonoRail.Framework"/>
  </configSections>

  <monorail>
    <controllers>
      <assembly>GettingStartedSample</assembly>
    </controllers>
    <viewEngine
      viewPathRoot="Views"
      customEngine="org.SharpTiles.Connectors.Monorail.TilesViewEngine, org.SharpTiles,
        Version=1.0.0.0, Culture=neutral, PublicKeyToken=null, processorArchitecture=MSIL" />
  </monorail>

  <system.web>
    <httpHandlers>
      <add verb="*" path="*.rails" type="Castle.MonoRail.Framework.MonoRailHttpHandlerFactory, Castle.MonoRail.Framework" />
      <add verb="*" path="*.tile" type="System.Web.HttpForbiddenHandler" />
    </httpHandlers>
    <httpModules>
      <add name="monorail" type="Castle.MonoRail.Framework.EngineContextModule, Castle.MonoRail.Framework" />
    </httpModules>
  <system.web>
</configuration>

To actually use the tiles view engine you have to register the TilesViewEngine. A good place to do this is in the Application_Start method in Global.asax.cs extension methods.

protected void Application_Start()
{
  new org.SharpTiles.Connectors.Monorail.TilesViewEngine().Init();
}

The final step in the preparation process is changing the Default.aspx file. Clear the entire file except for the page directive and overload the OnLoad method to redirect to the Monorail index page (which we will create later on):

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="GettingStartedSample._Default" %>

<script runat="server">
  protected override void OnLoad(EventArgs e)
  {
    Response.Redirect("~/home/index.rails");
    base.OnLoad(e);
  }
</script>

On the "getting started" page of Monorail (http://www.castleproject.org/monorail/gettingstarted/creatingproject.html) it's recommended to use the following folder structure in your Visual Studio solution:

Recommended folder structure for Monorail

For this tutorial we use the same folder structure as shown in the image above, but instead of the "Views\layouts" folder we use the "Views\shared" folder. The next step is creating a controller: create a controller class "HomeController" in the "Controllers" folder and make it look like this:

using Castle.MonoRail.Framework;

namespace GettingStartedSample.Controllers
{
  public class HomeController : SmartDispatcherController
  {
    public void Index()
    {
      PropertyBag["Title"] = "Home Page";
      PropertyBag["Message"] = "Welcome to Monorail!";
      RenderView("Index");
    }

    public void About()
    {
      PropertyBag["Title"] = "About Page";
      RenderView("About");
    }
  }
}

Well we are done with the code files; let's move on to configuring tiles. SharpTiles requires a tiles definition file which holds all definitions. You can either set the location in the Web.Config file or just make a file called tiles.xml as embedded resource and place it in your assembly. SharpTiles will scan the calling assembly looking for a tiles.xml file. For now we will use the assembly approach. Check out the tutorial section for different approaches. So lets make the file tiles.xml in the Views folder. Make sure it is an embedded resource. It should look like this:

<?xml version="1.0" encoding="utf-16"?>
<tiles xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <definitions>
    <tile name="home.Index" path="Views.Home.Index.tile" />
    <tile name="home.About" path="Views.Home.About.tile" />
  </definitions>
</tiles>

In the sample file we see two view names: 'home.Index' and 'home.About' (in italic). The default tile name is a concatenation of the controller name (lower case), a '.', and the action name (<controller>.<action>). Since Monorail formats controller names to lower case, the controller name part of the tile name needs to be in lower case for the controller to be found by SharpTiles. The highlighted blue parts are the tiles we have to implement. First we construct an Index.tile in Views\Home:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
  <head>
    <title>Index</title>
  </head>
  <body>
    <h1>${Title}</h1>
    <p>${Message}</p>
    <a id="link_to_about" href="home/About.rails">Next</a>
  </body>
</html>

Also create an About.tile in Views\Home:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
  <head>
    <title>Welcome</title>
  </head>
  <body>
    <h1>${Title}</h1>
    <p>Lorem ipsum dolor sit amet,
       consectetuer adipiscing elit, sed diam nonummy
       nibh euismod tincidunt ut laoreet dolore magna
       aliquam erat....</p>
  </body>
</html>

Make sure that the you make both files an 'Embedded Resource'. This should result in the following file structure:

File structure after completing previous steps

Voila, you are ready to run the solution.

Application showing the home screen

I can almost hear you think: "Hmmm wait a minute. I'm repeating myself. This can't be good". And you are correct. This is where tiles come in the picture.

Adding Tiles

What we're missing here is the base page. Let's introduce it together with our tile place holder. Create a file 'Layout.tile' in the 'Shared' folder with the following contents. Make sure that the you make it an 'Embedded Resource' and change your virtual path in you project settings to '/GettingStartedSample/'.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
    <title>${Title}</title>
    <style type="text/css">
      body
      {
        font-size: .75em;
        font-family: Verdana, Helvetica, Sans-Serif;
        background-color: #DDD;
      }

      p#logo
      {
        font-weight: bold;
        font-size: 24px !important;
        margin-bottom: 3px;
      }

      #main
      {
        padding: 5px 30px 15px 30px;
        background-color: #FFF;
      }

      #footer
      {
        text-align: center;
        font-size: .9em;
      }
    </style>
  </head>
  <body>
    <div>
      <div>
        <p id="logo">
          My Sample Monorail Application WITH TILES
        </p>
        <ul>
          <li>
            <a id="menu_to_index"
               href="/GettingStartedSample/home/Index.rails">Index</a>
          </li>
          <li>
            <a id="menu_to_about"
               href="/GettingStartedSample/home/About.rails">About</a>
          </li>
        </ul>
      </div>

      <div id="main">
        <div id="content">
          <tiles:insert name="body"/>
        </div>
        <div id="footer">
          <p>
            My Sample Monorail Application WITH TILES © Copyright 2010
          </p>
        </div>
      </div>
    </div>
  </body>
</html>

As you can see I highlighted all url's, (your first) SharpTiles insert tag and ${Title}. The <tiles:insert/> is the will be substituted with the content defined in the tiles.xml. For now you can think of it as the <asp:ContentPlaceHolder ID="MainContent" runat="server" /> on steriods. I will explain later.

The urls are highlighted because they have to be absolute and include the application name. If you don't want to mention the application name you got to use the <c:url/> tag. The next example shows the same menu as above but now with the use of the <c:url/>:

          <li>
            <a id="menu_to_index"
               href="<c:url value='~/home/Index.rails'/>">Index</a>
          </li>
          <li>
            <a id="menu_to_about"
               href="<c:url value='~/home/About.rails'/>">About</a>
          </li>

Okay we got rid of the repeating of the application name, but (in my opnion) we do pay a small price. The single quote is required for parsing purposes. So what's next? We got our new layout template in place. But our views 'Index' and 'About' still have the redudant html stuff in them. After some stripping the Index.tile should look like this:

<h1>${Title}</h1>
<p>${Message}</p>
<a id="link_to_about" href="/GettingStartedSample/home/About.rails">Next</a>

And the About.tile should look like this:

<h1>${Title}</h1>
<p>
  Lorem ipsum dolor sit amet,
  consectetuer adipiscing elit, sed diam nonummy
  nibh euismod tincidunt ut laoreet dolore magna
  aliquam erat....
</p>

Now we can put it all together. To make this all work we have to revisit the tiles.xml and redefine the 'Index' and 'About' definition. This is what we need to define:

  • Define the tile 'Layout.tile' as 'Default.Page.Layout'
  • Extend the tile 'Home.Index' from 'Default.Page.Layout'
      and use 'Index.tile' for the 'body' attribute.
  • Extend the tile 'Home.About' from 'Default.Page.Layout'
      and use 'About.tile' for the 'body' attribute.

Why is 'Index' extended from 'Default.Page.Layout'? We want the 'Index' file to use the same layout as defined in 'Default.Page.Layout'. Extending takes care of this. The revisited tiles.xml will look like this (note that the controller name part of the tile names is lower cased, as discussed before):

<?xml version="1.0" encoding="utf-16"?>
<tiles xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <definitions>
    <tile name="Default.Page.Layout" path="Views.Shared.Layout.tile" />

    <tile name="home.Index" extends="Default.Page.Layout">
      <attribute name="body" value="Views.Home.Index.tile"></attribute>
    </tile>

    <tile name="home.About" extends="Default.Page.Layout">
      <attribute name="body" value="Views.Home.About.tile"></attribute>
    </tile>
  </definitions>
</tiles>

And yet again you are ready to run the solution.

Application showing the home screen now with a layout template

This looks alright, doesn't it? I promised you that the layout was testable, without running the webserver. This is covered in the next block. If testing through the webserver is your need I would suggest using WatiN or Checkmate.

Writing a test

To be able to use tiles in tests, the tiles definitions have to be loaded. SharpTiles provides some functionality to help writing tests. The GuardInit loads the embedded tiles inside the provided assembly.

private IViewCache _cache;

[SetUp]
public void SetUp()
{
  _cache = new TilesCache().GuardInit(Assembly.GetAssembly(typeof(OutputConstraintTest)))
}

The tiles in the GettingStartedSample application are now loaded and available for testing (any IViewCache will do). The behaviour of the used cache differs. SharpTiles comes with the following two IViewCache implementations:

  • TilesCache - This cache preloads all the tiles from the tiles.xml; Views are retrieved by tile definition name.
  • NstlCache - This cache lazily loads all views; Views are retrieved by path name.
Let's write a simple test that checks the rendering of a complete tile.

Assert.That("Home.About"1,
  Output.
  Is.EqualTo.
  File("Views/Home/about.expected.full.html"2).
  UsingModel(new Hashtable { { "Title", "TEST TITLE" } }3)
  From(_cache)4);

  1. The tile to test;
  2. The file location with the expected result;
  3. The model to use for rendering;
  4. The cache which contains all the tiles.

Testing only an attribute of a tile can be achieved by <Tile name>@<Attribte name>, for example Home.About@body. Testing the full rendering of a tile (and its nested tiles) is specificly usefull for tesing small tiles and for refactoring bigger tiles. See the chapter about Refactoring a test.

When testing a tile with nested tiles, you don't want full rendering. We only want to unit test the tile and not be bothered with nested tiles. Full rendering will subject your tests to changes of used tiles and thereby make them unstable. How to prevent this problem? SharpTiles offers you a way to stub-out the used tiles.

Assert.That("Home.About",
  Output.
  Is.EqualTo.
  File("Views/Home/about.expected.tile.html").
  UsingModel(new Hashtable { { "Title", "TEST TITLE" } }).
  StubOutTiles().
  From(_cache));

SharpTiles also renders whitespaces, tabs, newlines, etc. This means that rendering a tile may add some unexpected white space characters. In HTML we're not so concerned about white space characters. This can complicate unit testing. When Output.Is.Like is used instead of Output.Is.EqualTo, all white space characters are ignored in the comparison.

This covers the basic of unit testing of tiles. Now you should be able to test driven develop and refactor the HTML rendering of your web application.

Refactoring

With testing available, refactoring is possible. The next tutorial shows how to extract a tile. First we capture the current output. We can do this by writing a test. If you download the source code you can use the tests inside the MonorailSample application.

Assert.That("Home.About",
  Output.
  Is.EqualTo.
  File("Views/Home/about.expected.full.html").
  UsingModel(new Hashtable { { "Title", "TEST TITLE" } }).
  StoreResultInFile(@"C:\output.html").
  From(_cache));

It's not advisable to keep this test. We only use it for refactoring. After running the test, the highlighted addition can be removed. Now we can start refactoring. We want to move the menu (highlighted in the sample below) on top of a tile.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
    <title>${Title}</title>
    <style type="text/css">
      body
      {
        font-size: .75em;
        font-family: Verdana, Helvetica, Sans-Serif;
        background-color: #DDD;
      }

      p#logo
      {
        font-weight: bold;
        font-size: 24px !important;
        margin-bottom: 3px;
      }

      #main
      {
        padding: 5px 30px 15px 30px;
        background-color: #FFF;
      }

      #footer
      {
        text-align: center;
        font-size: .9em;
      }
    </style>
  </head>
  <body>
    <div>
      <div>
        <p id="logo">
          My Sample Monorail Application WITH TILES
        </p>
        <ul>
          <li>
            <a id="menu_to_index"
               href="/GettingStartedSample/home/Index.rails">Index</a>
          </li>
          <li>
            <a id="menu_to_about"
               href="/GettingStartedSample/home/About.rails">About</a>
          </li>
        </ul>
      </div>

      <div id="main">
        <div id="content">
          <tiles:insert name="body"/>
        </div>
        <div id="footer">
          <p>
            My Sample Monorail Application WITH TILES © Copyright 2010
          </p>
        </div>
      </div>
    </div>
  </body>
</html>

To achieve this, copy the highlighted section into a new file '/Views/Home/Shared/Menu.tile' (don't forget to set it as embedded resource) and replace the highlighted section in 'Layout.tile' with

<tiles:insert name="menu"/>

The tiles are changed but we have to inject a tile with name 'menu' into to the Layout.tile. This must be done in the 'tiles.xml'. Change the line

<tile name="Default.Page.Layout" path="Views.Shared.Layout.tile"/>

into

<tile name="Default.Page.Layout" path="Views.Shared.Layout.tile">
  <attribute name="menu" value="Views.Shared.Menu.tile"/>
</tile>

Running the unit test probably will fail. This is because the template engine also renders spaces, enters, tabs, etc. We can fix this by using Like instead of EqualTo. Like ignores all whitespace characters.

Assert.That("Home.About",
  Output.
  Is.Like.
  File("Views/Home/about.expected.full.html").
  UsingModel(new Hashtable { { "Title", "TEST TITLE" } }).
  From(_cache));

Voila: the test is running and the refactoring is complete.

Contact form fixed

I regret reporting that the contact form was broken for the last week. All mails send to us were corrupted(empty). So reported bugs in the last week are not received. I'm aware that the timing with regards to the monorail release isn't very handy. The contact form is fixed now. I apologize.

4 February, 2010

Monorail released M5

Monorail is released. The release also contains some minor fixes.

23 January, 2010

Monorail almost finished

Maikel Willemse is coding a Monorail view engine. It's available in the nightly release. A tutorial and a new release will be available shortly.

2 January, 2010

No more tiles.xml
(if you like)

A NSTL view engine is now available. You can do SharpTiles without having a tiles.xml. You will lose the power of dependency injection. If you are interested check out the tutorial page.

21 July, 2009

Developer M3 released

Fixed a couple of parse bugs. Cleaned up refreshing of resources, resource locations and registration of the TilesViewEngine. Wrote some tutorials
Ronald Dehuysser contributed the <tiles:insertTemplate/> and <tiles:putAttribute/> tag.

8 June, 2009

HtmlHelper wrapped II

Updated tag reference documentation.

5 April, 2009

Ready for MVC RC 2

SharpTiles is now ready to use with MVC RC 2.

6 March, 2009

HtmlHelper wrapped

The MVC HtmlHelper is now wrapped and can be used. For it's only available in the nightly build. When the documentation and new Tutorials are made a new release will be made.

22 December, 2008

Ready for Beta 1

SharpTiles is now ready to use with MVC Beta 1. The tutorial page will be updated soon.

3 November, 2008

We've got Tiles

It took me a bit longer than expected but Milestone 1 is reached. Tiles are now available.

23 July, 2008

First release

Finally, after some hard work my pet project is ready for its initial release. Here it is! Please give it some thoughts and feel free to add comments.

23 May, 2008

SID: PopUp 0