Because we wanted 'nice' URLs containing year, month, day and post partial url, and our model contains release date as DateTime, I had to 'deconstruct' the DateTime object to separate fields.

I didn't like it when I wrote it, but at the time it was used in a single place. I have closed my eyes and leave it. But this week we have introduced RSS feed and so we needed to generate links to posts again, but in different view. This lead us to think, how can we unify this?

0) Close your eyes and copy-paste

No, I really do not mean this seriously, but it was the initial solution. Till the time I started work on our new homepage where we want a link to the latest blog post...

1) Shared/partial/model view

We can also use display view for model property.

@Html.DisplayFor(m => m.post, "PostLinkView")

And then create a cshtml view in the ~/Views/Shared/DisplayViews/PostListView.cshtml.

I think this is suitable for a little different scenario than the one we challenge here. This is best suited when a more complex HTML is needed, like when we want to share a whole template for the post preview.

2) Create extension method on HtmlHelper

This was our next thought. Just create a PostLink extension method on the HtmlHelper and pass in the release date and partial post URL.

@Html.PostLink("View post detail", post.PublishedAt, post.Slug)

Right after that, we would surely needed the same extension method on the UrlHelper. Pretty simple solution, but our Projects section has also a bit complicated URL.

3) Using model, aka C# class

Than we have realized that the object passed to the RouteLink or RouteUrl could be of any type, and that this type can even be a hand written class, not just anonymous class generated from new { ... }. So we have written a class.

And we liked it. We liked it vary much, because we can use this class for both HtmlHelper methods and UrlHelper methods. It is PORVO (Plain-Old-Route-Values-Object) and it can be used everywhere route values for blog post are needed. The constructor does all the dirty work in a one single place.

Roots of the AspNet (model) Navigation project

The hand written route values class inspired us to create a small NuGet package with support for creating links and urls, registering routes and do navigation in MVC projects using classes.

We have realized that a name of the route could be part of the class, as const field or using custom attribute. Also the route URL could be placed here and default values too. So we have added these fields to our BlogPostRouteValues.

This really awsome and simple and it only costs creating route values class. Currently we are linking to blog posts from three places. The number of code lines needed for original solution (with copy-paste) and model-based are the same, except that adding next link is much easier now and we are not copy-pasting, which is always good.

Designing the library

Constants are boring when they are read by reflection. Let's define them by attributes. Also, defaults are still defined using anonymous classes. Also, the route name can be taken from class name using some conventions.

RouteName is taken from the class name, so BlogPostRoute. This is because we used the RouteNameAttribute, but didn't pass in value. When didn't use it at all, the route will be registered without name.

RouteUrl is straightforwardly taken from the RouteUrlAttribute.

Defaults are defined using RouteDefaultAttribute. The RouteControllerAttribute is inherited from it and overrides a method for providing key-value pairs used as defaults.

This is bit of reflection that will be used not even when registering routes in the application startup, but every time the URL is needed for such model. It the same as with the class using constants shown previously.

To avoid this, we have introduced a IRouteModelProvider where reflection is used only for the first time the concrete type is used and then the cached model is used. Because there is no simple way to pass this collection to the extension methods on the HtmlHelper and UrlHelper, we use it as single (because it is only a cache that can be shared across whole application) from RouteModel.Provider.

With this collection (or call it service) we can introduce features like route model validation. We take the route url and test if all tokens have a property with the same name or a default key-value is provided. The validation is executed when the route model is created, so typically in the application startup and only once.

Both the collection and validation can be replaced or extended. The static class RouteModel contains method for registering new instance of IRouteModelProvider and the default implementation of it takes a IRouteModelValidator in the constructor.

Lastly, we have decided to introduce interfaces for all of the metadata defined using attributes, so custom attributes can be made and will work the default implementation of IRouteModelProvider. Every attribute defined on the route model class is casted to the IRouteNameProvider, IRouteUrlProvider and IRouteDefaultProvider. This way can introduce a single attribute that implements all the interfaces.

Summary

A simple problem of creating links to posts in the blog lead to a whole new project. This project takes a lot of type-safety to the world of MVC routing. It can work side by side with typical routes, so only those 'hard-ones' can be handled by classes.

Also when these route classes are defined as anemic POCO, with public setters, it is easy to use them in the action methods of controllers for reading parameters.