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:

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

This entry was posted in ASP.NET MVC, Featured, Presenting. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://codebetter.com/blogs/kyle.baley Kyle Baley

    It very well could be a step in the wrong direction. I admit I did this for no other reason than “because I could” and didn’t put too much thought into the practicality of it. And quite frankly, now that I have given it 45 seconds of pondering, I can’t think of any particular reason why I would switch from a querystring approach to a URL-based approach. But come on, it’s still shiny!

    In all seriousness, it’s probably reckless to make it seem like I’m advocating such an approach without giving the ramifications their proper due. Historically, I’ve usually used “let’s try this and see if it sticks” when playing with stuff that’s new to me which has served me well personally because I have no problem abandoning an idea if it doesn’t pan out.

    Also, I deliberately avoided using the term REST because I still don’t full understand the concept and I don’t want to contribute to what seems to be a growing overloading of the term.

  • http://scottic.us ScottBellware

    Kyle,

    This looks like a step in the wrong direction to me.

    An architecture with a URI-based API doesn’t mean that a component-level or object-level or call-level API is exposed as URIs. An API at the level of URIs is naturally different – and should be different as it reflects a different level of abstraction or indirection.

    A contemporary, RESTful address for one of your query URL’s might be more appropriately composed as:
    localhost/Suvius.Flamingo/songs?artist=Neil Young

    In a URI-addressable programmable web, we’re not moving everything into the URL path just `cause we can. There are still practices and idioms and conventions in play.

    Don’t wait for Phil and Rob to stop saying that RESTful routing is an orthogonal concern to a web resource framework before you look into URI-addressable API’s and resource modeling for yourself – even if it means – or especially if it means – that you’ve gotta go outside of .NET to find that knowledge.