MonoRail, AspView and ReSharper: skip ViewAtDesignTime

May 24, 2008 at 1:53 AMAndre Loker

AspView and Intellisense

AspView is a MonoRail viewengine which provides compile time checking of views and Intellisense support in Visual Studio. The latter is achieved by using the known ASP.NET WebForms syntax to define views, see below for an example:

aspview_viewatdesigntime

It uses the @Page directive and sets the Inherits attribute to a base class called ViewAtDesignTime defined by AspView. This base class contains fake methods and properties that are similar to the ones that are available to the runtime view class - Caslte.MonoRail.Views.AspView.AspViewBase. The only purpose of the ViewAtDesignTimeClass is to make Visual Studio's Intellisense work. It does, as can be seen in the picture. Additionally the user can provide a custom pair of design time and runtime classes that derive from ViewAtDesignTime and AspViewBase.

However, there is a caveat. ViewAtDesignTime derives from System.Web.UI.Page. This is a requirement of Visual Studio to make Intellisense work. As a result Intellisense will show all members of Page, which is at least confusing. More likely, it's misleading as many of the Members won't be available in AspViewBase and will let compilation fail.

aspview_intellisense

In the example above Intellisense shows members like DataBind which will not be available during compilation.

ReSharper to the rescue: skip ViewAtDesignTime

However, as Ken Egozi pointed out on the Castle Developer mailing list, if you use ReSharper (and you should) you can skip the ViewAtDesignTime class altogether and just use the AspViewBase class (or a derived class) in the view. ReSharper will still provide Intellisense. The list of members is much more reliable as those members will definitively be available during compilation.

aspview_resharper

Advantages of using the AspViewBase based class instead of ViewAtDesignTime:

  • Get better Intellisense: Page members are ommitted
  • Less code: if you provide your own base classes for views because you want to provide some extra members to the view (a good example would be the code generated by CodeGenerator) you do not have to provide a design time counterpart as well.
  • More robust code: similarly, if you have to provide a runtime class and a design time class you have to ensure that both interfaces match exactly, otherwise the design support is misleading (this problem does actually happen). Without the design time view, only one interface has to be maintained.

Disadvantages:

  • No Intellisense on dev machines without ReSharper (which is a good excuse to ask your boss for a R# licence anyway)

Thanks to Ken Egozi for this neat hint.

Posted in: Castle | Tools

Tags: , , ,

MonoRail and Windsor integration

May 18, 2008 at 9:00 PMAndre Loker

Although the procedure of enabling Windsor Container integration is explained quite well in the documentation, I ran into some trouble today. As required by the Windsor integration I made my HttpApplication instance implement IContainerAccessor, created a WindsorContainer instance in Application_Start and returned that instance in the Container property. Still the MonoRail runtime was complaining:

The container seems to be unavailable in your HttpApplication subclass

What went wrong? The problem was that the IWindsorContainer field in the HttpApplication was not static. As described in my previous post (which I only wrote just because of that problem) multiple instances of HttpApplication can be created by the ASP.NET runtime to serve multiple requests at once. As a consequence only the first instance of HttpApplication that was created had a valid container assigned to its field. The first subsequent request found only a null value and raised the exception mentioned above.

Register IContainerAccessor in Windsor

If you want to register your HttpApplication as a IContainerAccessor in Windsor I'd recommend something along this line:

   1: public class ContainerAccessor : IContainerAccessor {
   2:     public IWindsorContainer Container {
   3:         get {
   4:             // lookup container accessor 
   5:             return WindsorContainerAccessorUtil.ObtainContainer();
   6:         }
   7:     }
   8:  
   9:     public static void RegisterAtContainer(IWindsorContainer container){
  10:         container.AddComponentLifeStyle<IContainerAccessor, ContainerAccessor>(LifestyleType.Singleton);
  11:     }
  12: }

Of course you can always just register the container on itself to have it injected into components.

Posted in: Castle

Tags: , ,

Haml for MonoRail

May 16, 2008 at 12:20 AMAndre Loker

It would be soooo cool if there was an HAML based view engine for MonoRail. For ASP.NET MVC one is already being made.

The following example is shamelessly ripped off of Andrew Peter's site:

ASPX:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" 
    CodeBehind="List.aspx" Inherits="MvcApplication5.Views.Products.List" Title="Products" %>
<asp:Content ContentPlaceHolderID="MainContentPlaceHolder" runat="server">
  <h2><%= ViewData.CategoryName %></h2>
  <ul>
    <% foreach (var product in ViewData.Products) { %>
      <li>
        <%= product.ProductName %> 
        <div class="editlink">
          (<%= Html.ActionLink("Edit", new { Action="Edit", ID=product.ProductID })%>)
        </div>
      </li>
    <% } %>
  </ul>
  <%= Html.ActionLink("Add New Product", new { Action="New" }) %>
</asp:Content>

NHAML:

%h2= ViewData.CategoryName
%ul
  - foreach (var product in ViewData.Products)
    %li
      = product.ProductName 
      .editlink
        = Html.ActionLink("Edit", new { Action="Edit", ID=product.ProductID })
= Html.ActionLink("Add New Product", new { Action="New" })

MonoRail deserves this, too!

Update 6/6/2008: cool, there's an upcoming NHAML view engine for MonoRail

Posted in: Castle

Tags: , ,

Access to MonoRail service instances

May 6, 2008 at 10:18 PMAndre Loker

In my current MonoRail based project - which is in fact my first MonoRail project - I needed access to some of the services provided by the MonoRail container, for example, IEmailTemplateService and IEmailSender. Furthermore the MonoRail app had Windsor Integration enabled, so I wanted them to be injected into the client component by the Windsor IoC container. Unfortunately, the services are not registered as components at the Windsor container.

Luckily, I was pointed at the solution (e.g. here and here) by the kind users of the Castle Project Users mailing list. The key is to let your HttpApplication class implement IMonoRailContainerEvents, which will provide you with a IMonoRailContainer instance as soon as it is created and again when it is initialized.

   1: // Implements IContainerAccessor to notify MonoRail that we provide our own IWindsorContainer
   2: // Implements IMonoRailContainerEvents to be notified when the container was created
   3: public class Global : HttpApplication, IContainerAccessor,  IMonoRailContainerEvents {
   4:     private static Container container;
   5:  
   6:     #region IContainerAccessor Members
   7:     public IWindsorContainer WebContainer {
   8:         get { return container; }
   9:     }
  10:     #endregion
  11:  
  12:     #region IMonoRailContainerEvents Members
  13:     public void Initialized(IMonoRailContainer mr){
  14:         // here we register the services as components
  15:         container.Register(
  16:             Component.For<IEmailTemplateService>().Instance(mr.EmailTemplateService),
  17:             Component.For<IEmailSender>().Instance(mr.EmailSender)
  18:             // additional services here...
  19:         );
  20:     }
  21:  
  22:     public void Created(IMonoRailContainer mr){
  23:         // ignore..
  24:     }
  25:     #endregion
  26:  
  27:     protected void Application_Start(object sender, EventArgs e) {
  28:         // create container
  29:         container = new WebContainer();
  30:         container.Init();
  31:     }
  32: }

The important part is the fact that we implement IMonoRailContainerEvents. In the Initialized method, we can than access the service instances of the MonoRail container. The WebContainer class is a class derived from WindsorContainer that handles the Windsor integration for MonoRail.

   1: public class WebContainer : WindsorContainer {
   2:     public WebContainer()
   3:         : base(new XmlInterpreter(new ConfigResource())) {
   4:     }
   5:  
   6:     public void Init() {
   7:         AddFacility("rails", new MonoRailFacility());
   8:     }
   9: }

Simple like that. With all these pieces of code set up, I can now have the services be injected into my components, for example:

   1: public class DefaultMailService : IMailService {
   2:     private const string defaultFrom = "my application <mail@localhost>";
   3:     private const string defaultMailLayout = "Mail.vm";
   4:  
   5:     #region dependencies - Injected by Windsor
   6:     public IEmailTemplateService Templates { get; set; }
   7:     public IEmailSender Sender { get; set; }
   8:     #endregion
   9:  
  10:     #region IMailService Members
  11:     public void SendActivationMail(string name, string email, string ticket) {
  12:         log.InfoFormat("Sending activation mail to {0}", email);
  13:         var msg = Templates.RenderMailMessage("Activate", defaultMailLayout, new {name, ticket});
  14:         msg.To = email;
  15:         msg.From = defaultFrom;
  16:         Sender.Send(msg);
  17:     }
  18:     #endregion
  19: }

Thanks to the guys from the Castle mailing list.

Posted in: Castle | Snippets

Tags: , , , ,

MonoRail: Mapping root directory to specific controller and action

April 28, 2008 at 2:38 PMAndre Loker

Most often, you want a web application to transfer the user to a specific controller action when he or she accesses the root directory of your application. For example, if the user entered http://somedomain/TheWebApp you'd want the app to handle it as if the user entered http://somedomain/TheWebApp/Home/Index.

In MonoRail this can be easily achieved using the built in URL rewriting service. Setting up the configuration is a matter of two steps:

1. Enable rewriting rules for the application

To enable url rewriting in the first place, you will have to add a specific IHttpModule in your web.config. It should be the first module in the <system.web><httpModules> section to ensure proper working.

   1: <system.web>
   2:   <!-- other stuff -->
   3:   <httpModules>
   4:     <add name="routing" type="Castle.MonoRail.Framework.RoutingModule, Castle.MonoRail.Framework" />
   5:     <!-- other modules -->
   6:   </httpModules>
   7:   <!-- other stuff -->
   8: </system.web>

 

2. Configure rewriting rules

In your <monorail> section in web.config or - if you are like me - in the external monorail configuration file add a <routing> section like this:

   1: <routing excludeAppPath="true">
   2:   <rule>
   3:     <pattern>^/?$</pattern>
   4:     <replace>/home/index</replace>
   5:   </rule>
   6: </routing>

This section contains only one rule, that will rewrite any request that targets the root directory of the application (either with or without a trailing slash) to the index action of the home controller. Of course, you may fill in whatever route fits your needs.

If you are not familiar with regular expressions: the pattern ^/?$ can be read like this: the whole url (from the start ^ to the end $) must be either empty or may optionally consist of one slash (? means: the slash is optional).

The excludeAppPath attribute makes your life much easier if the app exists in a subdirectory instead of the root directory. It simply means that you may write your rules as if all urls are targeted at the root directory. Otherwise, you would have to write:

   1: <routing excludeAppPath="false">
   2:   <rule>
   3:     <pattern>^/TheWebApp/?$</pattern>
   4:     <replace>/TheWebApp/home/index</replace>
   5:   </rule>
   6: </routing>

This would be a lot harder to maintain and deploy.

Some notes:

  • The pattern does not match the query string part, so http://somedomain/TheWebApp/?foo=bar would still be correctly rewritten to /home/index
  • The query string is passed to the rewritten URL as well, so (in the example above), the query parameter foo would be usable in the Home controller
  • I work with the subversion repository trunk of the castle project, so I cannot guarantee that this works for the current officially released version (RC3 as of now) or that it will work in the future.

Posted in: Snippets | Castle

Tags: , ,