Author Archive for nick.nystrom

Ping Pong Pairing October Event

The October event for the Minneapolis/St. Paul .NET User Group was on Ping Pong Pair Programming, the combination of test-driven and pair-programming techniques in which one developer writes a unit test, a second developer writes application code to pass the test, and then they switch places.

I will be updating this post with the code written during the event, my short slide presentation, and possibly some other media.

Thanks again for attending my presentation, I hope you had a lot of fun. Please leave me any feedback on the event here, especially if you did or did not like the deviation from the standard lecture format of the group.

Presentation and Code

Using System.Web.Routing with Castle MonoRail

Several folks have already posted about using System.Web.Routing with standard ASP.NET WebForms, and I’d like to extend the concept for use with MonoRail. The default MonoRail router is not terribly sophisticated. It works by using pairs of regular expressions to match and then rewrite a url into /area/controller/action/id format. Here is an example from the documentation:

<rule>
	<pattern>(/blog/posts/)(\d+)/(\d+)/(.)*$</pattern>
	<replace><![CDATA[ /blog/view.rails?year=$2&month=$3 ]]></replace>
</rule>

It’s functional, and you can achieve most any route that you’d like, but the syntax is awkward. System.Web.Routing provides a much cleaner solution:

routes.Map(
   "MonthlyArchives",
   "/blog/posts/{year}/{month}",
   new { controller = "blog", action = "view" }
);

ScottGu gives a good tutorial.

Before we can get started, we’ll need to examine how both MonoRail and System.Web.Routing perform routing and IHttpHandler creation. Unfortunatley, an explanation of ASP.NET handlers and modules is beyond the scope of this post. Although dated, there are some good overviews.

System.Web.Routing runs as an IHttpModule, examines incoming requests, matches them against registered routes, and, if a match is found, uses the IRouteHandler associated with the matched route to create an IHttpHandler, which will process the request.

MonoRail on the other hand has a straightforward IHttpHandlerFactory implementation, MonoRailHttpHandlerFactory, which parses the request url for /area/controller/action.extension/id syntax. If the MonoRail routing module is installed, that module will rewrite the url and create a RouteMatch object in the current HttpContext.Items collection.

Therefore a successful integration will need to use the existing MonoRailHttpHandlerFactory infrastructure–directly creating controllers requires far too much supporting context which the factory provides–yet allow System.Web.Routing to inform the controller/action selection using its much richer RouteData output.

We’ll start by subclassing the MonoRailHttpHandlerFactory and adding the IRouteHandler interface:

public class MonoRailRouteHandler : MonoRailHttpHandlerFactory, IRouteHandler
{
    #region IRouteHandler Members

    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        var context = HttpContext.Current;
        return GetHandler(
            context,
            context.Request.RequestType,
            context.Request.RawUrl,
            context.Request.PhysicalApplicationPath);
    }

    #endregion
}

This handler is clearly just a passthrough to the underlying MonoRail GetHandler factory method. We’ll need to somehow use the routing information from System.Web.Routing to tell GetHandler what to create. Fortunately, Castle is very flexible here, and has a dependency, IUrlTokenizer, which takes a url and returns a UrlInfo structure. UrlInfo contains area, controller, and action properties (amongst other less interesting properties). Next lets build a IUrlTokenizer that just provides whichever UrlInfo it’s told to:

public class MockUrlTokenizer : IUrlTokenizer
{
    [ThreadStatic]
    private static UrlInfo _currentUrlInfo;

    public static UrlInfo CurrentUrlInfo
    {
        get { return _currentUrlInfo;}
        set { _currentUrlInfo = value; }
    }

    public void AddDefaultRule(string url, string area, string controller, string action)
    {
        throw new NotImplementedException();
    }

    public UrlInfo TokenizeUrl(string filePath, string pathInfo, Uri uri, bool isLocal, string appVirtualDir)
    {
        return CurrentUrlInfo;
    }
}

Finally we’ll modify our IRouteHandler to use the new UrlTokenizer as well as convert the System.Web.Routing RouteData to a MonoRail RouteMatch:

public class MonoRailRouteHandler : MonoRailHttpHandlerFactory, IRouteHandler
{
    public MonoRailRouteHandler()
    {
        UrlTokenizer = new MockUrlTokenizer();
    }

    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        var context = HttpContext.Current;

        // parse urlinfo from the s.w.routing data
        var values = requestContext.RouteData.Values;
        MockUrlTokenizer.CurrentUrlInfo = new UrlInfo(
            context.Request.Url.Host,
            context.Request.Url.Host,
            context.Request.ApplicationPath,
            context.Request.Url.Scheme,
            context.Request.Url.Port,
            context.Request.RawUrl,
            values.ContainsKey("area") ? (string)values["area"] : String.Empty,
            values.ContainsKey("controller") ? (string)values["controller"] : String.Empty,
            values.ContainsKey("action") ? (string)values["action"] : String.Empty,
            String.Empty,
            context.Request.Url.Query
        );

        // create a monorail routematch
        var match = new RouteMatch();
        foreach (var pair in values)
        {
            match.Parameters.Add(pair.Key, pair.Value as string);
        }
        context.Items[RouteMatch.RouteMatchKey] = match;

        // allow standard monorail handler to proceed
        return GetHandler(
            context,
            context.Request.RequestType,
            context.Request.RawUrl,
            context.Request.PhysicalApplicationPath);
    }
}

Now you can simply register routes normally, using the new MonoRailRouteHandler.

Go ahead and grab the source file which includes a couple of extras:
MonoRailRouteHandler.cs.




Creative Commons Attribution 3.0 United States
Creative Commons Attribution 3.0 United States