Fluent language for route registration.
But we can go even further. With a bit of expression trees and extension methods we can build a small language that would allow much more complicated registrations. Like this one:
routes.MapRoute("{locale}/{product}/view/{mode}/")
.On<ProductController>(x => x.Display(Detalization.Full))
.Default("locale=be-BY", "product=all")
.Constrain("locale", To.Regex("[a-z]{2}-[A-Z]{2}"))
.Constrain("mode", To.Enum<Detalization>());
So let's look how it's implemented inside. Here's the code for the fluent interface:
public static class MyExtensions
{
public static MyRoute MapRoute(this RouteCollection routes, string url)
{
var myRoute = new MyRoute(url);
routes.Add(myRoute.Route);
return myRoute;
}
public static MyRoute On<T>(this MyRoute myRoute, Expression<Action<T>> action)
where T : Controller
{
return myRoute.SetActionDefaults(action);
}
public static MyRoute Default(this MyRoute myRoute, params string[] param)
{
return myRoute.SetDefaults(param);
}
public static MyRoute Constrain(this MyRoute myRoute, string param, IRouteConstraint constraint)
{
return myRoute.SetConstraint(param, constraint);
}
}
It relies on a bit more verbose MyRoute class:
public class MyRoute
{
public Route Route { get; private set; }
public MyRoute(string url)
{
Route = new Route(url, new RouteValueDictionary(), new MvcRouteHandler());
}
internal MyRoute SetActionDefaults<T>(Expression<Action<T>> action)
{
Route.Defaults = ParseDefaults(action.Body as MethodCallExpression);
return this;
}
internal MyRoute SetDefaults(params string[] param)
{
foreach (var pair in ParseParameters(param)) {
Route.Defaults.Add(pair.Key, pair.Value);
}
return this;
}
internal MyRoute SetConstraint(string param, IRouteConstraint constraint)
{
var constraints = Route.Constraints ?? new RouteValueDictionary();
constraints.Add(param, constraint);
Route.Constraints = constraints;
return this;
}
//private ParseDefaults and ParseParameters omitted for brevity
}
As for class To, it's a simple container with static properties like Regex or Enum which return custom route constraints.
Finishing off.
So - what do you think? Give it a try - download the code, play with it, and let me know if you can think of any pros, cons and improvements.
I also want to mention that while working on this type safe registration thing, I stumbled upon two amazing blog posts from Gino Heyman. He doesn't use fluent interfaces but our implementations of type safe routes are fairly similar. Gino was the first one - check out his ideas: