RoutingConventions
A routing convention represents a rule of how to generate a route based on the action/controller matching a specified criteria. You can use conventions to build routes and to modify the generated routes. It makes it easier to decouple routing from controller/actions and to make bulk changes when needed (instead of changing manually each route or route attributes for each controller, you just change a convention). You can have as many conventions as you'd like, each fit for specific cases. The recommended way is to group the conventions into modules by deriving from RoutingConventionsModule .
public class AdminModule : RoutingConventionsModule
{
public override void Configure(IConfigureRoutingConventions conventions)
{
//namespace based urls for controller in the Admin namespace
conventions.If(ac =>ac.Controller.StripNamespaceAssemblyName().StartsWith("Admin"))
.Build(routeBuilderInfo=>{
var url=routeBuilderInfo.ActionCall.Controller.ToWebsiteRelativePath(GetType().Assembly)
.TrimStart('~')
.TrimStart('/')
.ToLowerInvariant();
var rt = routeBuilderInfo.CreateRoute(url);
return new[] {rt};
});
//if you have the controller named like its namespace, change it to 'main'
// something like 'admin/dashboard/dashboard' -> 'admin/dashboard/main'
conventions.If(ac =>
ac.Controller.Namespace.EndsWith(ac.Controller.ControllerNameWithoutSuffix())
)
.Modify((r, info) =>
{
var name = info.ActionCall.Controller.ControllerNameWithoutSuffix().ToLower();
if (r.Url.EndsWith(name))
{
r.Url = r.Url.Remove(r.Url.Length - name.Length) + "main";
}
});
}
}
If you prefer to be more structured then you can define builders and modifiers in their own classes, then tell a module to use them
public class ViewPageRouting : IBuildRoutes
{
public bool Match(ActionCall actionCall)
{
return actionCall.Controller == typeof(ViewPageController);
}
public IEnumerable<Route> Build(RouteBuilderInfo info)
{
var route = info.CreateRoute("{slug}");
route.Defaults["controller"] = "ViewPage";
return new[] { route };
}
}
//turns something like 'articles/listarticles' into 'articles/list'
// 'articles/articlesadd'->'articles/add'
public class RemoveDuplicateWords : IModifyRoute
{
public bool Match(ActionCall action)
{
var name = action.Controller.ControllerNameWithoutSuffix();
var namespaceEnding = GetNamespaceEnding(action);
return BeginsOrEndsWithWord(name,namespaceEnding) || BeginsOrEndsWithWord(name+"s",namespaceEnding);
}
private static string GetNamespaceEnding(ActionCall action)
{
return action.Controller.Namespace.Split('.').Last();
}
static bool BeginsOrEndsWithWord(string controller,string word)
{
return controller.StartsWith(word) || controller.EndsWith(word);
}
public void Modify(Route route, RouteBuilderInfo info)
{
var namespaceEnding = GetNamespaceEnding(info.ActionCall).ToLower();
var segments = route.Url.Split('/').Select(s =>
{
if (s.StartsWith(namespaceEnding) && s.Length != namespaceEnding.Length)
{
return s.Substring(namespaceEnding.Length);
}
if (s.EndsWith(namespaceEnding) && s.Length != namespaceEnding.Length)
{
return s.Substring(0,s.Length-namespaceEnding.Length);
}
if ((s + "s").EndsWith(namespaceEnding) && s.Length != namespaceEnding.Length-1)
{
return s.Substring(0,s.Length-namespaceEnding.Length+1);
}
return s;
});
route.Url = string.Join("/", segments);
}
}
public class BrowseModule : RoutingConventionsModule
{
public override void Configure(IConfigureRoutingConventions conventions)
{
conventions.Add(new ViewPageRouting());
conventions.Add(new RemoveDuplicateWords());
}
}
##Configuration In your favourite startup task (in this example, it's ConfigTask_Routing) you need to write only this
RoutingConventions.Configure(c =>
{
//required
c.RegisterControllers(typeof (ConfigTask_Routing).Assembly);
//you want just to add modules in the future
c.LoadModules(typeof (ConfigTask_Routing).Assembly);
//A predefined module that sets route http method constraints, based on method name
//(it starts with 'get' or 'post') and mvc attributes (HttpGet, HttpPost etc)
c.LoadModule(new SemanticConstraints());
//default route
c.HomeIs<HomeController>(h => h.Get(null));
});
Note that RoutingConventions just adds new routes to the existing routing table, it doesn't change anything, you can think of it as a route builder engine.
###WebApi For WebApi, the configuration looks a bit different
RoutingConventions.Configure(c =>
{
//required
c.RegisterControllers(typeof (ConfigTask_Routing).Assembly);
//you want just to add modules in the future
c.LoadModules(typeof (ConfigTask_Routing).Assembly);
//A predefined module that sets route http method constraints, based on method name (it starts with 'get' or 'post') and mvc attributes (HttpGet, HttpPost etc)
c.LoadModule(new SemanticConstraints());
},GlobalConfiguration.Configuration);