Ms MVC at CNUG

Yesterday at the Chicago .Net User Group meeting Nermin Dibek presented on the new ms mvc framework.  Several people were interested in a follow up session.  If you are one of those people and have stumbled onto this blog, or if you are local to Chicago and want to see some more stuff, feel free to ping me or email me and we can try and set something up.

Here is a list of links to get you started with Ms MVC.

Tags: ,

Haml Comes to MS MVC

Andrew Peter has created NHaml, a new view engine for MS MVC based on the ruby Haml dsl. Sweet!! Maybe we can get Andrew to drop this in the MVC Contrib project.

The community support for MS MVC has been awesome so far.

Tags: ,

MS MVC: Routing - The Good, The Bad, The RESTful

After playing with MS MVC for a little bit, and looking at routing extensively I have compiled a list of good things and things that need some improvement. The team has already said they will be doing work with routing, I am looking forward to seeing what they come up with.

What’s Good with MS MVC Routing?

Not Bound Explicitly To Controllers and Actions

An innovative approach to routing was not forcing the controller and action to be bound to the route. Instead they introduced two special parameters [controller] and [action] to indirectly bind the request. This drastically reduces the number of routes your application needs and means you do not have to register a route for every new controller.

Validation

Validation separates the url from matching on the parameters. It allows for full blown regular expressions without dirtying up the url and making it more difficult to generate urls from the routes. Hammett ran into this issue with his new routing module for monorail when he offered a straight regex rule. There was no way to pull parameters out of it.

Route Handlers

A very simple way to extend route resolution on incoming requests. Its not complicated and it gives you access to everything you need to do custom matching. I wish we could get something like that for generating urls.

Restful

Not much more to say hear that hasn’t already been said. This engine does make writing restful urls a breeze.

Testing

Thank you for making my routes so easy to test. I now have confidence that everything is working without having to fire up IIS and a browser to test by hand.

What’s Not So Good with MS MVC Routing?

No Duplicate Controller Names?

I can’t have two controllers in different namespaces with the same name?

Solution

Phil said they are fixing this. I would hope I could specify a Controller parameter either as a Type or a String where the string could be the full name of the controller. I would also maybe like this to be addressed with Areas… maybe.

Areas Where Are You?

There is no out of the box support for Areas. An area is simply a partition for your application. The most common one I can think of is http://localhost/admin/[controller] for separating all of your admin stuff from regular url’s.

Another scenario would be dropping in secondary applications, like a forum, blog, photo gallery, etc. Those will probably need to be defined under some Area.

Workarounds

RouteTable.Routes.Add(new Route
{
 Url = “admin/[controller]/[action]“,
 Defaults = new
 {
  Controller = “Admin“,
  Action = “Index”
 },
 Validation = new
 {
  Controller = “Admin|Users|Categories”
 },
 RouteHandler = typeof(MvcRouteHandler)
});

What we did above is hard code are “admin” area and then set a Validation regular expression on Controller. Now only the controllers listed will be accessible via those urls, and when generating a url it will have the correct prefix of “admin/” in the path.

What sucks about that? Every time I add a new controller that should be an admin controller I have to modify the regular expression. BTW, that regex is not case insensitive like the url.

Potential Solutions

  1. Allow a convention where the namespace dictates the area.
  2. Add an AreaAttribute similar to Monorail.

I like having the option of number 1, but I don’t want to be forced to this. It makes my urls feel to tightly coupled to my code. Its not a bad default convention as I am sure it will work the vast majority of the time.

Number 2 just doesn’t sit right with me but it seems like a decent option as well. The area has to come from somewhere, right?

One thing is for sure, there needs to be some type of Area parameter that is respected by the RouteCollection and based on the current implementation it looks like it would be very difficult for it to support areas deeper than 1 level. By default it is splitting on the “/” so an area covering “admin/maintenance” would be difficult to use with a single Area parameter.

Maybe it could support arrays which could get interesting…

[area]/[area]/[area]/[controller]/[action]
Defaults = new { Area = "admin/maintenance/daily" }
Validation = new { Area = "admin/maintenance/daily" }

Are We Really Blocking and Locking?

Reflecting over the code of the RouteCollection we can see that when an operation is performed like GetRouteData or GetUrl then the routes are locked until the proper route is matched. Every request coming into your application is going to call GetRouteData and every link you render in your views is going to call GetUrl. What does this mean. It means when a request comes in it locks the routes searching for a match. When a new request comes in on a separate thread it calls GetRouteData too and sits and waits until the first request found a match before it can proceed. And all the blocking happens for every link that you are rendering in your views too. Most pages will have 10 - 20 links, that’s lot of blocking, idle time, and context switches for the OS.

Solution

Don’t lock, period. Adding routes should go into a temp route collection. Add explicit methods for Build() or Rebuild() which can perform an atomic assignment operation on the temp routes to the routes in use. Can some kind of error happen if routes are switch in the middle of processing a request? Yes. What are the odds? Slim. If dynamically altering routes is something that my application requires, without restarting it, then let me handle that. Its an edge case.

If you have to lock, think about using a lock free collection, or use Reader/Writer locks as the number of reads is going to demolish the number of writes. Locking is just way too expensive to do on every request and every link render.

Why is Validation Case Sensitive?

Urls should be case insensitive. If I want to validate parameters using a regular expression those should be case insensitive to. Please don’t make me write Validation expression like: @”[eE][dD][iI][tT]” to match any combination of the string “Edit”.

Why is Validation Not Compiled?

Every request coming in is going to call GetRouteData, does it not make sense to compile my Validation expressions so they do not need to parsed every time they are evaluated?

Solution

if (!Regex.IsMatch(input, pattern))
{
 return false;
}

to

foreach(Regex validator in route.Validators)
{
 //…
 if(!validator.IsMatch(input))
 {
  return false;
 }
 //…
}

Where is the Extensibility?

No, not this extensibility, well maybe, give me the option please.

There are a number of edge cases popping up on blogs, comments, and forum posts.

  • What do I do if I need a different sub/domain?
  • What do I do if I need to redirect to a secure url from an insecure one?
  • What do I do if I need an absolute url instead of a relative one?
  • What do I do if I need to redirect to a non-standard port for https?

While the MVCRouteHandler is extendable the Route is not, nor is the RouteCollection. We need a way to override the matching AND url generating for specific rules.

Potential Solution

The RouteCollection looks a little heavy. I think it can delegate some of its responsibility to other services whose implementations can be swapped out. I also think it would be prudent to give us some interfaces and factories. Just overriding the GetRouteData and GetUrl is not enough. It would be great to have the ability to create our own RouteCollection so we could implement different algorithms for collecting and enumerating the routes to find a match?

Summary

All in all I am very pleased with routing in the first CTP. IMHO it is better than Monorail routing right now. It does need some pieces re-thought, re-factored, and de-debugged, but if it were perfect it wouldn’t be CTP and I wouldn’t be writing this.

Please let me know what I am missing and how crazy some of my solutions are.

+1 To the MS-MVC team, they are off to a great start.

Tags: , ,

MS MVC: Testing Routes

In Scott’s post about routing he showed how you can easily test routes. One cool thing about 3.5 asp.net extension framework is the introduction of new interfaces for IHttpContext, IHttpRequest, IHttpResponse, and some other. This makes testing with mocks so much easier. Here I will show you some tests I wrote to validate the routing rules we created for simply restful routing in the ms mvc framework.

What Are We Testing?

When testing we want to stick with one assertion per test. So for this example we are only testing that the rules we wrote set the proper action. You can view the rules we will be testing in my previous post.

Create The Test Fixture

using System.Collections.Specialized;
using System.Web;
using System.Web.Mvc;
using MVCContrib.SimplyRestful;
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
using Rhino.Mocks;

namespace MVCContrib.Specs.SimplyRestfulSpecs
{
  [TestFixture]
  public class SimplyRestfulRouteMatchTests
  {
  }
}

Add The First Test

    [Test]
    public void GetRouteData_WithAControllerAndIdUsingAHttpGetRequest_SetsTheShowAction()
    {
    }

Our test name is using pattern Method_Context_Result. So the method under test is GetRouteData. The context of the test is With a controller and id using a an http get request sets the show action. Now lets fill in the test.

    [Test]
    public void GetRouteData_WithAControllerAndIdUsingAHttpGetRequest_SetsTheShowAction()
    {
      IHttpContext httpContext;
      RouteCollection routeCollection = new RouteCollection();
      SimplyRestfulRouteHandler.InitializeRoutes(routeCollection);
      RouteData routeData = routeCollection.GetRouteData(httpContext);
      Assert.That(routeData.Values[”action“], Is.EqualTo(”show“).IgnoreCase);
    }

Above we wrote our assertion. We want to call the GetRouteData method on a routeCollection and we want to assert that the parameter named “action” is equal to show. At this point the test will fail with a nre on httpContext. httpContext needs to be an instance of IHttpContext, so how do we get an instance of an interface. Simple, we mock it. We will setup the mock context with just enough information for the RouteCollection to do its job. This could be considered a stub and not a mock since we are simply using Rhino.Mocks to setup results.

    [Test]
    public void GetRouteData_WithAControllerAndIdUsingAHttpGetRequest_SetsTheShowAction()
    {
      MockRepository mocks = new MockRepository();
      IHttpContext httpContext = mocks.DynamicMock<IHttpContext>();
      IHttpRequest httpRequest = mocks.DynamicMock<IHttpRequest>();

      RouteCollection routeCollection = new RouteCollection();
      SimplyRestfulRouteHandler.InitializeRoutes(routeCollection);

      using(mocks.Record())
      {
        SetupResult.For(httpContext.Request)
          .Return(httpRequest);
        SetupResult.For(httpRequest.AppRelativeCurrentExecutionFilePath)
          .Return(”~/products/123“);
        SetupResult.For(httpRequest.HttpMethod)
          .Return(”GET“);
      }
      using(mocks.Playback())
      {
        RouteData routeData = routeCollection.GetRouteData(httpContext);
        Assert.That(routeData.Values[”action“], Is.EqualTo(”show“).IgnoreCase);
      }
    }

So what just happened? First we created a MockRepository which is the core engine of Rhino.Mocks. This could probably get moved to a [SetUp] method. Next we create two Dynamic Mocks. One for the context and one for the request. We make them dynamic mocks because they are not under test. We do not care what gets called on these interfaces or how it gets called. We just care that if certain properties are called they return a specific values emulating what a real http request would look like. Next we have the mocks.Record phase. This is used to demarcate expectations versus assertions.

What to note is that the RouteCollection uses the AppRelativeCurrentExecutionFilePath property which expects a return string like “~/something/something/something.aspx” we are telling it to return “~/products/123″ products is our controller and 123 should be our id of the route. Next we setup a result for the Httpmethod property, remember our Route had validation on the special name Method which maps to a call on HttpMethod.

Finally we run our test and make the assertions. We enter playback mode, execute the GetRouteData method, which is our method under test, pass in the mocked context and assert that the returned routedata has a parameter named “action” = “show”

So once you add a couple more tests for different routes you will see a way to factor some common code out. I created a helper method to setup my context.

    private void SetupContext(string url, string httpMethod, string formMethod)
    {
      SetupResult.For(httpContext.Request).Return(httpRequest);
      SetupResult.For(httpRequest.AppRelativeCurrentExecutionFilePath).Return(url);
      SetupResult.For(httpRequest.HttpMethod).Return(httpMethod);
      if(!string.IsNullOrEmpty(formMethod))
      {
        NameValueCollection form = new NameValueCollection();
        form.Add(”_method“, formMethod);
        SetupResult.For(httpRequest.Form).Return(form);
      }
    }

Then my tests simply look like this.

    [Test]
    public void GetRouteData_WithAControllerAndIdUsingFormMethodPut_UsesSimplyRestfulRouteHandler()
    {
      using (mocks.Record())
      {
        SetupContext(”~/controller/123“, “POST“, “PUT“);
      }
      using (mocks.Playback())
      {
        RouteData routeData = routeCollection.GetRouteData(httpContext);
        Assert.That(routeData.Route.RouteHandler, Is.EqualTo(typeof(SimplyRestfulRouteHandler)));
      }
    }

The best part about writing tests for this stuff is you no longer have to fire up a browser and test by hand. You get instant verification that runs quick.

If you are wondering how I figured out what to mock and setup results for it was a quick perusal of System.Web.Extensions in reflector. Having to open reflector to figure out what to mock is a code smell for me. You really shouldn’t be mocking things you don’t own or control but this is a nice quick integration test that can save you tons of time down the road. Just don’t be surprised if after an update you get some errors because MS changed the implementation of RouteCollection.GetRouteData, another great reason for using a ContextSetup method.

Tags: , ,

How to Speed Up Build Times With NHQG and MSBuild

I love Ayende’s Rhino projects, especially his NHibernate Query Generator (NHQG). Since on most of my active development I am use NHibernate, Castle, and Rhino.Commons trunks, I keep nhqg in a tools directory and I was using a PreBuild command to generate the query builders. This had the negative impact of always rebuilding my domain project, even if a change was only made in another project. So, here is an msbuild snippet to generate my query builders only when my NHibernate mapping files change.

<ItemGroup>
 <NH-Mappings Include=“DomainQuery***.hbm.xml” />
</ItemGroup>
<Target Name=“BeforeBuild”
 Inputs=“@(NH-Mappings)”
 Outputs=“@(NH-Mappings -> ‘%(RelativeDir)Generated%(Filename).cs’)”>
  <Exec Command=“$(SolutionDir)..toolsnhqgNHQG.exe /lang:cs /files:%22$(ProjectDir)DomainQuery*.hbm.xml%22 /out:%22$(ProjectDir)DomainQueryGenerated%22″ />
</Target>

The Inputs and Ouputs of the Target is what allows msbuild to conditionally run the task. I use an msbuild transform to convert my mapping file name into the nhqg generated file name. Also, notice the %22 in command. This is the only way I could figure out how to pass quotes to a cmd inside of msbuild.

And the confirmation…

Target BeforeBuild:
  Skipping target "BeforeBuild" because all output files are up-to-date with respect to the input files.
  Input files: DomainQueryPost.hbm.xml;DomainQueryContributor.hbm.xml;DomainQueryTag.hbm.xml;DomainQueryItem.hbm.xml;DomainQueryBlog.hbm.xml;DomainQueryTagStat.hbm.xml
  Output files: DomainQueryGeneratedItem.hbm.cs;DomainQueryGeneratedTagStat.hbm.cs;DomainQueryGeneratedContributor.hbm.cs;DomainQueryGeneratedBlog.hbm.cs;DomainQueryGeneratedPost.hbm.cs;DomainQueryGeneratedTag.hbm.cs

Happy Building ;)

Tags: , , , ,

Beauty = Monorail + Validation + Active Record + (IE * Grief)

So I started playing with monorail today. It’s a very intelligent platform that I will continue to blog about. I got a sample setup very quickly thanks to Ayende’s
screen cast. The next step I wanted to play with was validation. Hamilton Verissimo has a nice screen cast showing off the new functionality with javascript agnostic code. Very cool that MR insulates you from a particular javascript library.

Two problems quickly arose:

  1. MR Validation does not like AR Validators
  2. IE did not like hammett’s use of <p> tags in the demo. Change to <div> or use a nicer layout with lists.

Read more »

Tags: , ,