Building View Components For MonoRail

time to read 64 min | 12633 words

While WebForms and MonoRail share the ability to refactor common UI elements into reusable components, the design decisions that should be considered for each of those is quite different. Build view components for MonoRail is much simpler than building controls for web forms, for one :-)

I talked in the past about the mechanics of implementing View Components, now I want to talk about several best practices that may be useful for developers building view components for MonoRail. For this discussion, I am going to give examples from the grid component for MonoRail, whose implementation can be found here and here. In what seems like the traditional manner for MonoRail code, the grid components comes in two flavors, GridComponent, that contains the basic functionality, and SmartGridComponent, that contains a lot more smarts and assumptions. There is nothing particularily interesting in this decision, it is just a way to split the responsabilities in a clear manner.

View Components are all about the UI, and I am going to talk about general components, not something specific for a single page/use-case. A lot of what I want to cover is about allow easy extensability and re-use of the component, which is not an issue if you are building it for a single use case only.

In order to understand view components, we first need to understand a bit about the view engines in MonoRail. The view engines (broadly) have the following concepts:

  • Template / View - will generate the HTML for a request. Similar to an aspx page.
  • Sub template / Sub view - is called from a parent template/view in order to handle some of the processing. Similar to an ascx control. However, a sub view is also a fully fledged view, so you can use it to render a page in one scenario, and embed it in another page in a second scenario (quite useful, by the way).
  • View Component - responsible to handle the rendering of a piece of HTML, similar to a server control. May contains sections (which can be required or optional).
  • Section - Resides inside a view components, allow to pass the view component a template that it can render at will. The best analogy from the web forms world are templates (<ItemTemplate> in a repeater, for instance).

Now that we have covered the concepts, let us move into the implementation. A grid is very common is web applications, and it is a very simple to build. You have a data source, and you simply want to generate the headers and rows tags accordingly.

A rudimentary implementation of that would give us a repeater, which can be as simple as:

public class RepeaterComponent : ViewComponent

{

      public override bool SupportsSection(string name)

      {

            return "item".Equals(name, StringComparison.CurrentCultureIgnoreCase);

      }

 

      public override void Initialize()

      {

            if(Context.HasSection("item")==false)

            {

                  throw new ArgumentException("RepeaterComponent must have an 'item' section");

            }

      }

 

      public override void Render()

      {

            IEnumerable source = ComponentParams["source"] as IEnumerable;

            if (source == null)

                  throw new ArgumentException(
                     
"RepeaterComponent must have an enumerable 'source' parameter.");

            foreach (object item in source)

            {

                  PropertyBag["item"] = item;

                  Context.RenderSection("item");

            }

      }

}

You can use this view component like this:

<%

component RepeaterComponent, {@source: customers}:

      section item:

            output item.CompantName

      end

end

%>

This should give you an idea about how the interaction between the view and the view component works. We pass arguments to the view component in the declaration, and we pass sections in the body. The view component can then access them via the ComponentParams variable and the Context, respectively. Note the foreach inside the Render() method, we assign the the variable "item" to the PropertyBag, which would expose it to the section, and then we render the section, which can now access the "item" variable that we just assigned.

I would never write such a component, because this is much simpler to understand, and does the same thing:

<%

for item in customers:

      output item.CompantName

end

%>

We need the repeater in WebForms, because there is no way to iterate over a parameter, nor easy way to pass data between the markup and the code.

The overall structure of a grid is similar to this:

<table> <!-- table start -->

      <tr><!-- header -->

            <th>Header 1</th>

            <th>Header 2</th>

      </tr>

      <tr><!-- data rows -->

            <td>column 1</td>

            <td>column 2</td>

      </tr>

</table><!-- table end -->

<!-- pagination -->

<b>first</b> | <b>prev</b> | <b>next</b> | <b>last</b>

I am sure that you already know of this, so why am I boring you with this? Well, in order to build a reusable piece of code, you need to allow customization of this basic structure. While you could build a component that would generate everything except the data rows, that would be of very limit use. Off the top of my head, I want to be able to control cell spacing and padding, the border and padding, the table CSS class, etc...

You can pass parameters to the component to control the rendering of the control, and indeed, this is the way that the GridView is using. On last count, the GridView has 85 properties, most of which can be used to control the output of the GridView. Trying to build that is going to take a long time, and it is going to be a very complex task. That doesn't really fit the MonoRail (or Castle in general) philosophy.

The Reflector output of the GridView goes well beyond 3,500 lines of code. I have built full blown systems that had less lines of code than that. (Just to compare, the entire Rhino Mocks code base now stands at about 7,900 Lines.

Let us take a look at the Render() method of the GridComponent, shall we?

public override void Render()

{

      IEnumerable source = ComponentParams["source"] as IEnumerable;

      if (source == null)

      {

            throw new ViewComponentException(

                  "The grid requires an IEnumerable parameter named 'source' ");

      }

 

      ShowStartTable();

      ShowHeader(source);

 

      ShowRows(source);

 

      ShowFooter();

      ShowEndTable();

 

      IPaginatedPage page = source as IPaginatedPage;

      if (page != null)

      {

            ShowPagination(page);

      }

}

We can see that we are following the same structure as above, but with methods name. Remember all the options I wanted to set on the <table> tag alone? How do I handle it with this component? I certainly can't give it up, and I most certainly don't want to start writing > 3,000 lines of code to cover each eventuallity.

Well, it turns out that there is another way to express what I want the <table> tag to look like. Are you ready?  Sure that you are ready?

Well, it turns out that I can expression what I want the <table> tag to look like using a brand new concept called HTML. Here is how I can do this:

<%

component SmartGridComponent, {@source: customers}:

      section tableStart:

%>

      <table cellpadding="2" cellspacing="0" style="border: dashed 2px red">

<%

      end

end

%>

This also shows inline HTML inside a section, but this isn't as important as the concept. We express what we want in the native language. We don't need some translation layer in between. This turn out to simplify quite a bit of my life.

How does the ShowStartTable() method looks like?

private void ShowStartTable()

{

      if (Context.HasSection("tablestart"))

      {

            Context.RenderSection("tablestart");

      }

      else

      {

            RenderText("<table id='grid'>");

      }

}

This approach, sensible defaults with the ease of overriding them, means that it is:

  • Extremely easy to build components (a few examples)
  • Very easy to use components

Gaining Some Smarts

Everything I said so far is possible in WebForms, it is just alien to the way Microsoft positioned WebForms. Now let us move from the realms of the obvious to realm of really cool stuff. We now move from talking about GridComponent to talking about SmartGridComponent. SmartGridComponent inherits from GridComponent and handles a lot of the details of renderring the UI. Mostly, it moves the level that we need to handle from the entire grid to single properties of the object. Think about it like turning AutoGenerateColumns to true in the GridView, except that it is not like that at all ;-)

A View Component is free to define what sections it can support, and there is not compile time limitation. Using this tidbit, we can start doing more interesting things with convention over configuration. Let us say that I want to display a list of customers, and I want to customize the header of the customer id column in some manner. I can do it like this:

<%

component SmartGridComponent, {@source: customers}:

      section customerIdHeader:

            output "<th>Id</th>"

      end

end

%>

By specifying a section with the property name postfixed with "Header", I can override the renderring of this column with my own code. This is a simple example of replacing the text, but I could put anything there at all, including complex HTML or calling a sub view or another view compoennt.

Under the same principal, I can override the rendering of the column itself:

<%

component SmartGridComponent, {@source: customers}:

      section customerId:

            output "<td><b>${item}</b></td>"

      end

end

%>

Again, this just shows the customer id in bold, but anything it possible here. Let us see what we need to do to make this happen, shall we?

Here is a small section from the code that is responsible for rendering the headers of the grid:

foreach (PropertyInfo property in this.properties)

{

      string overrideSection = property.Name + "Header";

      if (Context.HasSection(overrideSection))

      {

            Context.RenderSection(overrideSection);

            continue;

      }

      RenderText("<th class='grid_header'>");

      RenderText(SplitPascalCase(property.Name));

      RenderText("</th>");

}

We check the existance of an overriding section, and defer to it if it exists. The same goes for the rendering the column itself:

foreach (PropertyInfo property in properties)

{

      if (Context.HasSection(property.Name))

      {

            PropertyBag["item"] = property.GetValue(item, null);

            Context.RenderSection(property.Name);

            continue;

      }

      RenderText("<td>");

      object val = property.GetValue(item, null) ?? "";

      RenderText(val.ToString());

      RenderText("</td>");

}

The technical details are very simple, which is very good, but the power and flexibility that they bring is quite amazing.

The combination of convention over configuration, and the ease of overriding the defaults is a key stregth to building complex UI easily. It is important to note that when we actually build the UI, we never really leave the realm of HTML, what we write is very close to what would be sent to the browser. This give a lot more control over the final output, but not at the expense of having to deal with additional complexity.

I would like to end with a quote (via Avery):

Simplicity is about subtracting the obvious, and adding the meaningful.