Skip to content
Mihai Mogosanu edited this page Jun 25, 2014 · 7 revisions

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);