Welcome back to my on-going series about the creation of GeoGeekTV.com. Having solidified the design, we can move on to the implementation.
Since ASP.NET MVC abstracts the requested URL from actual files on disk (as in /foo/bar.htm does not actually load a the bar.htm file in the foo folder), we have a lot more control of the Urls. Thus, it’s worth taking a little time at the beginning of a project to decide how we want the Urls to work
For GeoGeekTV, we have a few main areas: Home Page, the Live Stream, Upcoming Shows, the Archive, a Contact page, Backstage and Admin.
Since most of these are pretty simple pages, we can use the default routing for the most part.
The default route uses the /{controller}/{action}/{id} pattern to make an incoming request to a controller and action (aka a method).
When we setup routes, we assign defaults to the components of the route. So for our site, we users to land at the home page, so we setup the default for the route to be “home”. As for a default action, we usually use “index”. Since an id does not have much context on some controllers, we default that to an empty string.
This is what the route looks like in the Global.asax.cs file:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
I like to map out the urls for an application in a table. This helps get an idea of how the routes should be setup, and it also provides a development road map for the controllers and actions we’ll need.
| Url | Controller | Action | Description |
| /Home | Home | Index | The home page |
| /Live | Live | Index | The live streaming page. If we are not live, show a list of recent shows |
| /Archive or /Archive/List |
Archive | List | Paged list of Archive shows |
| /Archive/View/{name} | Archive | View | View a show from the archive |
| /Archive/tag/{tag} | Archive | tag | View list of shows that have a particular tag |
| /Contact | Contact | Index | The Contact form |
| /Backstage | Backstage | Index | The backstage page |
| /Upcoming | Upcoming | List | List of upcoming shows |
| /Upcoming/Edit/{id} | Upcoming | Edit | Edit an upcoming show |
| /Upcoming/Create | Upcoming | Create | Create an upcoming show entry |
| /Archive/Create | Archive | Create | Create an entry in the archive |
| /Archive/Edit/{id} | Archive | Edit | Edit an entry in the archive |
| /Login | Login | Index | Login screen |
| /Admin | Admin | Index | Main Admin Screen |
Conveniently, for GeoGeekTV.com we can pretty much live with this default route, with one exception. We are going to support a simple tagging system on the shows, and it would be handy to have a url that would list all the shows related to a tag. Now, if we wanted to use the ID of the Tag in the url (i.e. the “Unit Testing” tag has an ID of 27, so the url would be /Archive/Tag/27) our default route would still work. But I think it would be more useful to have human readable tags, so we’ll use the actual tag in the url.
So I’ll create an additional route.
routes.MapRoute(
"Archive", // Route name
"Archive/Tag/{tag}", // URL with parameters
new { controller = "Archive", action = "Tag", tag = "" } // Parameter defaults
);
This new route is very specific – it only applies to the /Archive/Tag url, and will now pass in a parameter named “tag” to the controller method.
Testing the Routes
We can write unit tests to ensure that the routing is working as designed. Since this is just testing the routing, we don’t actually need to create any controllers or views – we are just testing the routes in Global.asax.cs
[Test]
public void archive_tag_tag_route()
{
MvcApplication.RegisterRoutes(RouteTable.Routes);
var httpContext = MockRepository.GenerateStub<HttpContextBase>();
httpContext.Stub(x => x.Request).Return(MockRepository.GenerateStub<HttpRequestBase>());
httpContext.Request.Stub(x => x.PathInfo).Return("");
httpContext.Request.Stub(x => x.AppRelativeCurrentExecutionFilePath).Return("~/Archive/Tag/foo");
var routeData = RouteTable.Routes.GetRouteData(httpContext);
Assert.AreEqual(routeData.Values["controller"], ("Archive"));
Assert.AreEqual(routeData.Values["action"], ("Tag"));
Assert.AreEqual(routeData.Values["tag"], ("foo"));
}
Although this is somewhat long-winded, and there are other more elegant options, I just keep this snipped of code lying around and drop it in as needed.
Although pretty simple at this point, I’ve pushed this into the repository over on assembla.com if you want to check out the code.
Up Next:
We’ve already created a simple out of the box ASP.NET MVC 2 web application. Next time we’ll layout the other projects in the solution, clear out the un-needed cruft from the MVC project and setup our dependency injection. From there we’ll actually start writing some code!