Friday, July 18, 2008

Upgrading Code Camp Server to Preview 4 - Part 2

Last time I went through some steps to upgrade Code Camp Server to the latest version of the ASP.NET MVC Framework.  I had to stop because MVCContrib hadn't been upgraded yet.  Now that their 4.0 release is out, I can continue.

Final Changes

I updated the mvccontrib binaries in our /bin folder and then ran all of the tests.  All of the tests passed.  Running the site I came to find a few errors in our views that referenced ComponentController (which should have been caught with tests... Note to self:  precompile the site in the NAnt build...).

 

....And the site is alive again!

site-live

We're now done with the upgrade, all tests are passing, and we need to commit our final changes to the branch we created in part 1.

svn-commit

Now we're ready to merge our changes back into the trunk. 

Merging the branch back to the trunk

To do this we will switch back over to the trunk with TortoiseSVN.

switch-back-to-trunk

Next, we open the Merge dialog from within the trunk.  With TortoiseSVN you're always merging from another branch to your current working copy.

TortoiseSVN asks you how you want to merge your changes.  What we've done is directly described in the second option, so we'll choose that.

merge-options1

Next we select the branch from where we'd like to pull the changes...

merge-options2 

The last dialog has some other options for you, but the defaults will be fine for us.

image 

Now click on merge & cross your fingers!

merge-error

Doh!

We get an error because googlecode doesn't yet support subversion 1.5.  Well that sucks, who planned this blog post anyway?

Time to drop down to command line to do the merge.  That's right, don't be afraid of the command line.  The first thing to do is to find out the revision that the code was branched.  You can do that with a command on the branch working copy itself, by typing:

svn log --stop-on-copy

or I can cheat and look at the screen shot of the merge in part 1.  I can see that the revision is 265.  Now, back on the trunk I can type:

svn merge -r 265:HEAD href="http://codecampserver.googlecode.com/svn/branches/mvcpreview4upgrade

The result:

cmd-line-merge

Running the tests again just to make sure everything worked, and it does!

Now to commit our changes into the trunk...

svn commit -m "integrating preview4 branch"

...And we're done!

I hope that my play-by-play upgrade has been helpful.  Dealing with frequent updates to the projects we use is a price that we pay for the rapidly changing environment that we live in.  Now, off to play with Preview 4 new features and hopefully get to delete some of our newly obsoleted code!

Wednesday, July 16, 2008

Upgrading Code Camp Server to Preview 4

I'm currently upgrading Code Camp Server to Preview 4, and I thought it might be useful to some to see how to go about managing a (somewhat) volatile upgrade to a project.

Step 1: Create a branch for your work

You don't want these changes to break the build or anyone else's pending changes.  Often times in a long, complicated upgrade there will be many parts that need fixing and you can't do it in one sitting.  Creating a branch for your changes isolates you from the trunk.

First this to do is make sure your local working copy is up to date and has no pending changes.  I created a branch called mvcpreview4upgrade and switched my working copy to that branch.

mvcpreview4upgrade-branch

Now we're free to make some breaking changes.

Step 2:  Upgrade the binaries to the new version of the framework

In Code Camp Server, all external dependencies are placed in the bin folder:

upgrading-binaries

All of the projects get their references from here.  We will definitely have to update MVCContrib to their latest once it is upgraded to Preview 4.  I'll leave this to last.

Step 3:  Get it compiling

Normally I'd suggest the command line NAnt build because it will run all the tests, but I'm fully expecting compile errors and I want to be able to double click on them and navigate directly to it.

Here are the issues I notice immediately:

  • ComponentController has been removed completely.  This hasn't been updated in a long while and we kind of expected this to happen.  Unfortunately we had 2 components that used this, so we have to re-implement them using another mechanism (I think I see one already, but I need to investigate more).
  • TempDataDictionary now has a default constructor, so we don't have to create a fake http context to build it.  Finally!

That seems to be it, I fixed these issues and it compiles.  Now to run the NAnt build (with all tests).

Step 4: Get the tests passing

Note to self:  need to upgrade all the old mocking syntax to the new RhinoMocks syntax.

nant-build

We can see that a unit test is failing (and we can also see a couple tests that I'm ignoring for now because ComponentController was removed).

Since the FlashMessageComponent needs to be changed anyway, I'll just ignore this test for now until we can finish the upgrade.

Another test was failing in the IntegrationTests assembly which was fixed by deleting this line:

test-resolving-component-controller

This test is used to make sure that we can build all controllers (with dependencies) at runtime.

After fixing this we are now all passing!

build-succeeding

Now it's time to take a look at the changed features...

Step 5:  Implement the new changes

Browsing the readme that is included with the release, we can see some changes in the following areas:

  • New Membership
    • not sure if we want to take advantage of this since we've already implemented it
  • Routes
    • They are now namespaced to avoid issues with reflecting over the entire assembly
  • AuthorizationFilter
    • I can remove the custom one we have
  • ErrorHandling Filter
    • We were going to end up using [Rescue] from MVCContrib, but now I think we can use this
  • OutputCache Filter
    • This will be better used once we have the functionality completed and we know where the best places to implement caching will be
  • Ajax
    • These look like ASP.NET Ajax improvements.  Not sure if they'll apply to us since we use jquery.
  • ActionInvoker
    • GetFiltersForAction method
      • We can probably better test the integration of custom action filters with this
  • ViewData
    • Eval - we'll probably just stick to ViewDataExtensions from MVCContrib for strong typing
  • ComponentController has been removed in favor on a RenderAction method on the view
    • this means we can probably replace the sponsorcomponent controller with a standard Controller implementation

Updating routes:

updating-route-namspaces

I removed another remnant of ComponentController (looks like this came from MVCContrib).  I also added the new namespace reference here.

For authorization we had previously created 2 attributes, [AdminOnly], and [RequireLogin].  RequireLogin will be replaced with the new [Authorize] attribute, however our AdminOnly attribute works a little differently, so some more work will be needed to get this to work with just a role name.

Our implementation won't be complete until we get MVCContrib integrated, so that'll have to wait.  But for now we're at a good place to commit our work!

Why go through all this pain?

Upgrading projects to frequently released dependencies can be a pain, but I think it's a small price to pay for the constant flow of improvements we're getting from ASP.NET MVC releases.  When the framework settles a bit more these updates will be mostly topical... meaning they won't sweep the carpet out from under your feet.

ASP.NET MVC Preview 4 Available

This will be 1 of 4 billion posts you read on this subject, but ASP.NET MVC Preview 4 is now available.  Read Scott Gu's initial notes and then go download it.

Over the next few days I'll be working on converting CodeCampServer and a few other small projects to preview 4.  Good stuff!

Monday, July 14, 2008

Book Review - Agile Estimating and Planning

I finally finished Mike Cohn's book, Agile Estimating and Planning.  It has been on my reading list for quite a while, but I just finished it last week.  In short I found it to be very helpful.  To understand how, I'll note some of my background in Scrum.

When I first read Agile Project Management with ScrumI was very intrigued.  I found that Scrum melded well with the mostly engineering-focused XP practices that I had previously adopted.

I then took the Certified ScrumMaster course, and I was even more impressed.  The problem, though, was that the course is only 3 days.  3 Days just isn't enough time to consider anyone a "master" of scrum.  It takes years of attempts, trying different things, failing, and overcoming those failures to eventually become  a "master."  But that's a topic for another day.  My point is, that there was still a lot for me to learn.

I gave some internal training on scrum and noticed that there were a lot of questions that I didn't have the answer to (how absurd of me to think that I had all the answers!).  Some of them were practical questions... like what's the best way to determine your iteration size, or how to effectively do release planning.  I knew the concepts, and I knew their purpose, but I struggled with the actual implementation.

After reading Agile Estimating and Planning, I am much more prepared to answer those questions (and implement them in my daily work).  Mike talks in depth about why fixed iteration length is important (which is a question that has come up twice in my project).  He also outlines how to effectively plan an iteration, and what to do if you have too much work planned. 

A co-worker called me once and asked me, "What happens if you partially implement a story during a sprint?" -- the answer came directly out of this book.  The short answer is that you can either move the entire story over to the next iteration (because it isn't done) or you can decide to split the story in two (however this method doesn't work if you implemented functionality but didn't test, for example).

While reading this book I have cracked it open three or four more times to refer to specific advice, which is why I think so highly of this book.  Once you understand the Scrum principles and have a healthy bank of questions about its implementation, turn to this book to get some concrete answers.  Not all of the advice is solid gold, mind you... but I think it's an excellent resource for a team that is new to Scrum.

Houston ALT.NET Geek Dinner #2 - Tonight

I'm so lame.  I forgot to post this last week.  If you don't want to be lame like me, then you should head out to Star Pizza tonight at 6pm for some fun-loving tech discussions about ALT.NET and the Houston community.  There will be pizza.  There will be beer.  And there will be a fat guy in a jump suit giving away iPhone 3G's*.

Potential Topics:



Star Pizza
2111 Norfolk
Houston, TX
Date: (Tonight!) July 14th, 2008
Time: 6pm

View Larger Map


* No iPhones will be given away.  And there won't be a fat guy in a jump suit.  I hope.


Friday, July 04, 2008

Prototype Protips #2 - Preventing Memory Leaks & Creating Objects

It’s been almost a year since I posted Prototype Protips #1 (and since then I have grown quite fond of jQuery), but prototype remains an excellent, choice for elegant javascript development still today.

If you aren’t taking advantage of an advanced javascript library, then you are not only missing out on cleaner code, but you are quite possibly likely introducing memory leaks through unfreed event handlers!  IE6 is notoriously bad at this, and you have to take extra caution in EVERY event handler to make sure you aren’t leaking memory.

Douglas Crockford, describes memory leaks in IE like this:

When a DOM object contains a reference to a JavaScript object (such an event handling function), and when that JavaScript object contains a reference to that DOM object, then a cyclic structure is formed. This is not in itself a problem. At such time as there are no other references to the DOM object and the event handler, then the garbage collector (an automatic memory resource manager) will reclaim them both, allowing their space to be reallocated. The JavaScript garbage collector understands about cycles and is not confused by them. Unfortunately, IE's DOM is not managed by JScript. It has its own memory manager that does not understand about cycles and so gets very confused. As a result, when cycles occur, memory reclamation does not occur. The memory that is not reclaimed is said to have leaked. Over time, this can result in memory starvation.

It takes a seriously smart person to understand closures in javascript and why they cause memory leaks.  Personally I’m glad someone else has figured it out!  But it doesn’t mean we can’t protect ourselves and just do things the safe way.

That’s why using a good javascript library is such a no-brainer.  In prototype, you wire up events like this:

Event.observe(window, 'load', function() {
    //do something when the DOM is loaded
});

This is a safe way of adding a load event to the page without erasing any previous load events that were wired up.  (Anyone ever had ASP.NET erase one of your button click handlers?  I have…)

$() extended elements also get this ability.  Say you have a button on the page like this:

<input type="button" id="activate" value="Activate the Reactor!" />

You can wire up a click even to this button by simply…

$('activate').observe('click', function() {
   alert('RUN!');
});

Wiring up events in this manner will help keep you from leaking memory and generally provides better syntax.

Objects

Sometimes when you are writing code that has complex javascript, the functions seem to relate, however they are just placed in file in global scope.  This breaks down when you have lots of scripts from various components on a page and sooner or later things will start to conflict.  I mean, how many init() methods can you have on a page?  Would you write a C# program with nothing but 1 class?

Grouping this functionality into javascript classes can really help streamline and encapsulate your javascript code.  This is incredibly useful when you have a script for a control on a page but you need to support having multiple instances of that control on the same page.  On top of that, with ASP.NET you have to deal with the id munging, so things become even harder.

In prototype (as of 1.6), you create a class like this:

var Person = Class.create({
    initialize: function(name, ) {
        this.name = name;
    },

    greet: function() {
        alert("Hello, " + this.name + "!");
    }
});

//create a person instance like this:
var joe = new Person("Joe");
joe.greet();   // -->  "Hello, Joe!"

The initialize() method is like a constructor.  It will get called when you “new” up your javascript class.  You can extend this object and add more functionality to it pretty easily.

Using Javascript Classes for your Components

Let’s say you want to create a type-ahead auto-complete textbox using ASP.NET.  Ignoring some of the canned-solutions for the sake of discussion, let’s also say we have to write it from scratch.  We’ll need some script to monitor the value of the textbox, and to send an ajax request with the current value, and to handle the response & fill in a list of values.

Here’s an example of how (a very crude) implementation might look:

 

//referenced (or rendered) only once
var TypeAheadTextbox = Class.create({
    initialize: function(textbox_id, indicator_id, results_id) {
        this.textbox = $(textbox_id);
        this.indicator = $(indicator_id);
        this.results = $(results_id);        
        
        //use .bindAsEventListener to preserve the 'this' pointer
        this.textbox.observe('keyup', this.processKey.bindAsEventListener(this));        
    },
    
    processKey: function() {
        //ignoring the interval, we'll just fire an ajax request for every letter
        var value = $F(this.textbox);
        this.indicator.show();
        new Ajax.Request('/pirate_ships/find', {
            method: 'get',
            params: 'filter=' + value,
            onComplete: function() { this.indicator.hide(); },
            onFailure: function(e) { alert("Error with ajax request: " + e); },
            onSuccess: function(response) {
                this.results.innerHTML = response.responseText;                
            }
        })
    }    
});


//initializing multiple controls
Event.observe(window, 'load', function(){
    var textbox1_autocomplete = new TypeAheadTextbox(
        '<%= txtbox1.ClientID %>', 
        '<%= indicator1.ClientID %>',
        '<%= results1.ClientID %>'
    );
    
    var textbox2_autocomplete = new TypeAheadTextbox(
        '<%= txtbox2.ClientID %>', 
        '<%= indicator2.ClientID %>',
        '<%= results2.ClientID %>'
    );
});

Of course this code is incomplete, but it’s definitely a start, and it amazes me at how simple it is.  One hiccup that I ran across doing something similar was the .bindAsEventListener noise.  You can read about that here, but the short of it is that javascript will lose ‘this’ in those types of event handlers.  Using .bindAsEventListener will fix this for you.Another trick is to use something like:

initialize: function(msg) {
    var self = this; //for use later
    this.msg = msg;
},

foo: function() {
    alert(self.msg);
}

We’ve also isolated the area where we need to pass in the generated ClientID of the controls we want to interact with.  This snippet of code can reside on the page, whereas the rest of the code reside ina separate file.

I’m no javascript ninja, but I have a vast appreciation for all that these libraries give us.  Yet it still amazes me that a lot of people don’t use them!  So go tell a friend or co-worker about the benefits of a good javascript libary.

Wednesday, July 02, 2008

Precompiling ASP.NET Applications And The Dreaded LoadResource Failure Error

This is a very strange problem that burned quite a bit of my time last week.  Hopefully this will be found by someone else with a similar dilemma.

Basically I’ve got a central web application project that I use to house user controls to be shared by many applications.  To accomplish this, I’ve followed this post by K. Scott Allen (of OdeToCode fame).

Since precompiling an ASP.NET project produces (at least) one funkily-named dll, I decided to ILMerge it with the code-behind assembly.  So the process looks like this:

After-build:

  • Precompile the website, updatable=false, into a temporary directory
  • Merge App_Web*.dll & my project assembly together into one
  • Reference this in another project & use the controls

If you look at this dll in reflector, you get something like this:

Reflector1

As a side-note, If you haven’t ever tried doing this before then you should know that you have to reference the compiled user control classes from the ASP namespace in your pages… so in this example <cc1:keywordgrid_ascx /> rather than <cc1:KeywordGrid />.

This first control worked just fine.  It was basically a custom GridView control.  Then I added that next control, SelectKeywords.  Adding it caused the assembly to crash upon loading, i.e. even when we weren't using SelectKeywords it was crashing pages that used KeywordGrid.  The error:

Error1

This happened in the constructor of any control in the assembly.  Digging a little deeper into the error, I found that this happens when your user control has literal content in it that gets embedded as a Win32 resource file during precompilation.

In reflector, this is what it looked like:

Reflector2

When building the control tree, the compiler was embedding literal content as a resource.  To try and fix the issue, I started removing literal content.  Bit-by-bit I removed some HTML until the error went away.  This is what reflector looked like when it was working:

Reflector3

But where is this resource file?  Clearly the assembly is referencing some resource file somewhere, right?  I peeked into Temporary ASP.NET Files to find the answer.

Temp-aspnet-files

If you open this up in Notepad, you’ll find your missing literals!

Notepad-resource

For some reason, this file wasn’t making it into the final assembly. 

So if you have more than 256 characters of contiguous literal content, the compiler will put this into a resource file (likely for perf reasons).  Sadly there is no way to turn this off. 

I’m not sure if ILMerging the assembly was the reason why this was getting lost, but luckily David Ebbo from Microsoft was able to provide me with a couple of options.

  1. You can break up the literal content by injecting empty code blocks like <% %> but who would really want to do that?
  2. You can fake out the compiler & supply a code provider that claims to not support Win32 Resource files.  This is what I chose.

I created a class (of course in another assembly, otherwise you have a chicken-and-egg problem):

public class CustomCodeDomProviderWithoutWin32ResSupport : CSharpCodeProvider
{
    public override bool Supports(GeneratorSupport generatorSupport)
    {
        if ((generatorSupport & GeneratorSupport.Win32Resources) == generatorSupport)
           return false; 

        return base.Supports(generatorSupport);
    }
}

To get the precompiler to use this new class, add this to your web.config:

<system.codedom>
    <compilers>
      <compiler language="c#;cs;csharp" extension=".cs" type="MyAssembly.CustomCodeDomProviderWithoutWin32ResSupport, MyAssembly" warningLevel="1" />
    </compilers>
</system.codedom>

Now my user controls can have as much literal content as they need and resources will not get added.  I’d like to thank David Ebbo for his assistance in helping me figure this out.  Thanks David!

Wednesday, June 04, 2008

.NET Database Migration Tool Roundup

A very long time ago I posted about my excitement that the .NET community was starting to see some love in terms of database migrations.

Fast forward more than a year and we have the following options for doing database migrations in .NET.

If I’ve left off one, I apologize.

That’s a crazy amount of options, and of course it beg’s the question: Which one should I use?

There are pros and cons of each, and for my specific needs I wound up choosing Migrator.NET.  I thought I’d post my thoughts on each tool here.

Rails Migrations

When I first raised the question of the lack of good migration tools for .NET a few people pointed out that there’s nothing stopping me from running the standard Rails migrations on SQL Server.  This is done through installing a gem.  However at 90% of my clients (or more) the use of Ruby is way too Alpha Geek to actually gain buy-in from the business.  This is a shame, but a current reality.  In addition, Rails migrations get really powerful when you leverage ActiveRecord classes within your migrations.  Since we aren’t using Rails at all, this isn’t possible.  Additionally, earlier versions of Rails migrations did not support foreign keys.  That’s a serious killer for a lot of folks.  I’m not sure if it has changed.

Rails Migrations look like this:

class AddSystemSettings < ActiveRecord::Migration
    def self.up
      create_table :system_settings do |t|
        t.string  :name
        t.string  :label
        t.text  :value
        t.string  :type
        t.integer  :position
      end

      SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
    end

    def self.down
      drop_table :system_settings
    end
  end


taken from: http://api.rubyonrails.org/classes/ActiveRecord/Migration.html

Running migrations is as easy as the command:

rake db:migrate 

RikMigrations

RikMigrations was the first one I tried.  They have an excellent fluent API, and allows you to build tables and columns by chaining together commands.  Unfortunately, they didn’t seem to have SQL 2005 Schema support and that is something that I require.  I could probably send them a patch, but I decided to continue my search.

using RikMigrations;
[assembly: Migration(typeof(BlogMigration1), 1)]
[assembly: Migration(typeof(BlogMigration2), 2)]
namespace Blog.Migrations
{
  public class BlogMigration1 : IMigration
  {
    public void Up(DbProvider db)
    {
        Table t = db.AddTable("Blog");
        t.AddColumn("ID", typeof(int)).PrimaryKey().AutoGenerate();
        t.AddColumn("Name", typeof(string), 64);
        t.Save();
    }
    
public void Down(DbProvider db) { db.DropTable("Blog"); }
}
  public class BlogMigration2 : IMigration
  {
     public void Up(DbProvider db)
     {
         Table t = db.AlterTable("Blog");
         t.AddColumn("Description", typeof(string), int.MaxValue);
         t.Save();
     }
     
public void
Down(DbProvider db) { Table t = db.AlterTable("Blog"); t.DropColumn("Description"); t.Save(); } } }

Running this migration is done like so:

Rikmigrations-cmd-line

Question:  How did it know which migrations to run?  Answer:  They created a table to keep track of it for you…

Rik-migrations-module-versions

The cool thing about RikMigrations is you can have versions of different modules, which could really help if you have unrelated areas of the application that each need their own version.  My current project could use something like this to separate unrelated application schemas, while maintaing only a single migration process.


Tarantino

Tarantino also seemed compelling, and it’s the tool we’re using with CodeCampServer, however I think that using a fluent API in C# is a little more compelling (however you’re at the whim of the API’s feature-set, unless they let you execute raw sql also, which many do).  Tarantino works by storing scripts that are named like 0001_add_customers_table.sql and it runs them in the right sequence.  This works well if you would like to continue to leverage your GUI tools such as SQL Server Management Studio.  Then you leverage a tool such as RedGate SQL Compare to generate the change scripts.  These scripts are generally large and not very readable, however this method is quite common and effective.

Here an example of what this looks like:

Tarantino-scripts

And these files are run using a NAnt task…

Tarantino-nant-task

Running this task gives output similar to this:

Tarantino-cmd-line

You can see that it – like RikMigrations – knew that it had already exceuted Version 1 of the database schema, so it could safely skip the script.  This is done in a similar manner to the other tools by using a table to record the current version of the database.

SubSonic Migrations

SubSonic migrations seemed like the perfect choice for my project, since we use Subsonic already.  However I’m running a bastardized version and it’s preventing me from updating to the latest trunk.  Having two different versions of Subsonic as dependencies feels dirty and confusing to other developers.

With Subsonic Migrations you don’t have a project that contains your migrations, you just have a folder with classes.  The filename carries the weight here, not the class name or attribute.  This appeals to me and feels a bit liberating, with my rails-envy and all.  However it does feel a bit unnatural to handle migrations outside of a project in our solution.

The API in Subsonic migrations is my least favorite so far, but not by much.  All of these tools have similar API’s, it just took me longer to grok this one.

For an intro on how to use this tool, check out Rob Conery’s post on the subject.

Migrator.NET

This project has had an… interesting lifecycle.  Originally I believe it was part of the Castle Project, maintained by Macournoyer, then it drifted over to Nick Hemsley.  The project is now up at google code.  Migrator.NET was easy to get into and the API was very simple and easy to grok.

The API for Migrator.NET isn’t as fluent as RikMigrations, but it did have builtin schema support for SQL Server 2005, which I needed.  The Attribute on the class also seemed a lot cleaner than the assembly attribute in RikMigrations.

Migratordotnet-syntax

Running the migrations was easy as well with the Migrator.Console.exe command line utility.  The output is nothing new, so I’ll spare you the screenshot.

Machine Migrations

This one looks pretty good, however not as mature as some of the other tools (I think it’s the newest on the scene).  Machine Migration relies on an MSBuild task to migrate your database.  It is .NET 3.5 only and thus, not available for my project It is 2.0, my mistake!Hammet has a short review here, and subsequently patched it to provide a few additional features.

I may start using Machine Migrations on a side-project I’m working on so that I can get a better idea of the differences it has with other frameworks.

Where’s DB Pro / Data Dude / Visual Studio 2005 Team Edition for Database Professionals ?

Ahh, the tool that loves to piss me off continually is somehow missing from this list?  That’s because this tool just doesn’t facilitate a good evolutionary style of database development.  I have honestly tried to work effectively with it and it consistently adds friction to our process.  I’ve talked to a bunch of other users and they all pretty much say the same thing:  it doesn’t work like that.  And that’s really sad, because 2008 is no different.  Sure it’s better, and as far as MSFT V2 products go I think this will be the poster child for v1 suckage, but 2008 still doesn’t have migrations.  Today I saw on Gert Draper’s blog some info on the latest out of band release, which includes some awesome changes, however there is no peep of anything resembling evolutionary development with the database.

This omission tells me that the DB Pro team is only listening to one community (the large enterprise customer) and completely ignoring what’s going on in the agile camp.  Maybe Phil Haack & Scott Gu could take over once MVC ships and provide some real change in that organization.

Anyway, rant over.

Migrations are an effective tool for frictionless automation & evolutionary database development especially with multiple developers.  With all of these tools out there, it’s hard to choose which one will fit your process.  I think the best advice is to just pick a tool and spike it, see what works and what’s painful.

Wednesday, May 28, 2008

ALT.NET Geek Dinner Recap

The pizza was good and the conversation was even better.  We had 9 people show up and we talked a lot about tech, Microsoft, Test Driven Development, and beer!

I brought up the subject of possibly organizing an ALT.NET Open Spaces conference here in Houston, and folks seemed interested.  I'm a little frightened by the amount of work this will be, but in the end I think it will be worth it.  We also decided on getting a google group setup, so if you're in the Houston area (or just want to listen in) then join us at http://groups.google.com/group/houstonaltdotnet.  We'll use this for future planning.

Unfortunately I forgot to take a picture of the group, whoops!  I guess that just means we'll have to meet again :)

Tuesday, May 27, 2008

Using ASP.NET MVC on IIS 6 without the .MVC Extension

One awesome features of ASP.NET MVC is the clean URLs.  If you aren't familiar with them, here is a sample:

/products/list

(versus a typical /ProductList.aspx)

The default route for ASP.NET MVC looks like this:

/{controller}/{action}

Using this route you can map most of the URLs that your application will need.  There is a problem, however, when running ASP.NET MVC on IIS6 (and 5).  The URL above doesn't have an extension, so IIS will assume that it is a virtual directory on the server.  We have a couple of options we can do to get around this:

  • We could create wild card mappings and pass every request through ASP.NET, however this is not recommended for performance reasons
  • We can add an extension that maps to ASP.NET (.mvc)

In the latter option your routes must change to something like this:

/{controller}.mvc/{action}

So URLs generated with this route will look like:

/products.mvc/list

...which basically reverts our clean urls into, well... less clean ones.

So if you want to deploy to Windows Server 2003 & IIS 6, you're out of luck.  Well, mostly...

URL Rewriting to the Rescue

We can leverage an ISAPI filter to rewrite URLs which will allow us to have the nice URLs while still on IIS 6.  There are 2 major products that do this for IIS:

ISAPI Rewrite is the more mature of the two, however it's not free.  They do provide a free version, but it doesn't support regular expressions, so it's pretty useless.

Jeff Atwood has a nice write-up of these two utilities.  In it he notes that the syntax for Ionic's ISAPI Rewrite is a little stranger and doesn't support the regex that we're all used to (since it uses a C regex library, not a .NET one).

I decided to choose Ionic's because it was free, however there was a lack of information on how to structure URLs, and it required a lot of trial and error to get it working.  Luckily they have an automatic logging facility that tells you how the rules are matching up.

For ASP.NET MVC, I needed to cover these cases:

  • A request for / should be redirected to /home
    This isn't really required, as .NET can do this for me, however I wanted to ensure that all entry points are at the same URL to avoid Page Rank issues.  (See the same Atwood post for information on how important this is)
  • A request for /something should be rewritten to /something.mvc
    That is, the user will request it without an extension, but the filter will rewrite it without the user's address bar ever changing.
  • A request for /something/index should be rewritten to /something.mvc/index
    Just making sure that URLs with actions get the extension only on the first part
  • The content directory that contains our javascript, CSS, and images should be excluded from the above rule
    Otherwise we'd have /content.mvc/styles.css which would be interpreted as a controller, rather than a direct file request.

I found a good resource that outlined how to do this with ISAPI Rewrite, however the rules were quite different with Ionic's.

Here are my rules for Ionic's ISAPI Rewrite that works for ASP.NET MVC:

# empty URL gets mapped to home controller 
RewriteRule  ^/$                 /home [R] # map controller parts of urls to .mvc, ignoring the content directory
RewriteRule  ^(?!/Content)(/[A-Za-z0-9_-]+)(/.*)?$          $1.mvc$2  [I]

It turns out that I only need 2 rules to satisfy the above requirements.  The first rule forces the browser to redirect, which will aid in making sure that I only have 1 entry point to my website.

The 2nd rule takes the first part of the ULR and adds .mvc to it, appending the remaining verbatim (if any).  It also excludes anything beginning with "content," so I'm free to put images, javascript, css, and other literal file resources there.

The last requirement is to define my routes with both .mvc and regular formats.  The trick is to define them in the right order.

routes.MapRoute("basic", "{controller}/{action}", new { ... } );

routes.MapRoute("basic_mvc", "{controller}.mvc/{action}", new {...});

Doing this ensures that our .MVC routes will actually function, but when we ask the framework for a URL (such as with Url.Action() or Html.ActionLink) we are handed the extension-less route (since it is defined first).

Route Testing is IMPORTANT

I've said it before and I'll say it again.  Route testing is important.  A single tiny change can break an entire application.  Applying automated tests is critical for any MVC application.  Since deployment of an ASP.NET MVC application needs to be flexible, your application should function with either type of route (extensions or not) so that you have flexibility of deployment.

Hard coding a route in 1 single location will prevent you from doing this.  Did I mention route testing is important?