West Palm Beach and the Fluent URLs
Ah, it's good to be back on the circuit. The Hillbilly made his first speaking engagement since May '06 earlier this week, thanks to the brave and trusting souls behind the South Florida .NET user groups. They have themselves a nice group out West Palm Beach way and I hope I can come back either there or one of the other places they're in charge of. I can only hope this last meeting was as fateful as my last presentation.
The official topic was ASP.NET MVC but we ventured into TDD, DDD, productivity tools, and AJAX if memory serves. Definitely played out differently than I expected but that's why speaking is so fun. Lots left uncovered but that just makes my presentation at South Florida Code Camp next week that much more interesting.
The code can be found here (3.8 Mb). It's a music catalog type application that lets you search for music in your library and play it, although only a song at a time. Check the ReadMe.txt for instructions on building. The short version, in theory, is: modify local-properties.xml and run clicktobuild.bat and you're good to go, assuming you have some version of SQL Server installed. It will create an IIS virtual directory pointing to your music folder to allow you to play the songs you select.
Before I start rambling on one of the more interesting aspects of the presentation, some acknowledgements. These are the main links I used in preparing both the code and the presentation:
- Scott Guthrie's Intro Series (Link is to part 4, which contains links to the other three parts)
- Phil Haack on testing controller actions
- Rob Conery talks on the MVCToolkit
Special thanks to Jeff Palermo's CodeCampServer from which I borrowed very heavily. I didn't go as deeply as he did in the architecture (unit tests? Pffft) and I switched out StructureMap for Windsor because it's what I know. Having said that, I've seen enough sample code for StructureMap that it's near the top of my list of things to check out in the near future.
The rest of this post discusses something I showed, albeit briefly, during the talk. It's the idea of "Fluent URLs".
It started when I created a search controller based on Scott Guthrie's sample search router. In this version, I'm searching a database of songs. Here is the route:
RouteTable.Routes.Add(new Route { Url = "Search/[query]", Defaults = new { controller="Search", action = "Results" }, RouteHandler = typeof(MvcRouteHandler) });
Here are some sample URLs that use this route:
localhost/Suvius.Flamingo/Search/Brubeck
localhost/Suvius.Flamingo/Search/Folk
localhost/Suvius.Flamingo/Search/Muppet Movie Soundtrack
And so on and so forth. The idea is that you pass in a [query] and it will find any song containing that text in the title, artist, album, whatever.
After that, I got to thinkin'. What if we wanted a more specific query that search based on, say, artist only. So I added the route:
RouteTable.Routes.Add(new Route { Url = "Search/FindSongsSungBy/[artist]", Defaults = new { controller="Search", action = "FindSongsSungBy" }, RouteHandler = typeof(MvcRouteHandler) });
And some sample URLs:
localhost/Suvius.Flamingo/Search/FindSongsSungBy/Dean Martin
localhost/Suvius.Flamingo/Search/FindSongsSungBy/Sarah Mclachlan
localhost/Suvius.Flamingo/Search/FindSongsSungBy/Mary Poppins
That route didn't last long due to the obvious duplication in the action. It's practically begging you to refactor into something more generic like:
RouteTable.Routes.Add(new Route { Url = "Search/[searchCriteria]/[queryText]", Defaults = new { controller="Search" }, RouteHandler = typeof(MvcRouteHandler) });
In this case, searchCriteria can be one of: Title, Artist, Album, Genre, or whatever criteria you wish to search by.
I didn't even implement this one because that's where the idea of a fluent URL came in. The new route:
RouteTable.Routes.Add(new Route { Url = "FindSongsWhere/[searchCriteria]/Is/[artist]", Defaults = new { controller="Search" }, RouteHandler = typeof(MvcRouteHandler) });
And some sample URLs:
localhost/Suvius.Flamingo/FindSongsWhere/Artist/Is/Neil Young
localhost/Suvius.Flamingo/FindSongsWhere/Title/Is/Come Away With Me
localhost/Suvius.Flamingo/FindSongsWhere/Genre/Is/Disco Funk
localhost/Suvius.Flamingo/FindSongsWhere/Album/Is/William Shatner Sings The Hits
I didn't take it any further than that (by, say, parameterizing the /Is/ into options such as /Contains/ or /IsSimilarTo/).
I will leave it to someone more practical than I to determine the usefulness of something like this but coming up with it did make me feel ever so giddy that such a thing was even possible. Not that I'd advocate this being the sole interface to your search engine but at the very least, it does lend itself to quick launch tools like Launchy and SlickRun where you can set up parameterized URLs in a more intuitive way. On the downside, it's nigh impossible to use things like Url.Action when you insert random, hard-coded words (like "Is") into the Url for the route.
A final important note on the code. It uses Castle Windsor to create the controllers and inject services into them and it uses NHibernate for the data access layer. I remember my first exposure to both of these fairly vividly and if you are not familiar with them, they can be ever so slightly unintuitive. As it is, I'm no NHibernate expert which is why this code took three days to prepare for release. If there is anything about these aspects (or any others) that you need clarification on, please contact me and I'll do my best to edify and elucidate.
Kyle the Routed