How do you inherit route prefixes at the controller class level in WebApi? How do you inherit route prefixes at the controller class level in WebApi? asp.net asp.net

How do you inherit route prefixes at the controller class level in WebApi?


I had a similar requirement. What i did was:

public class CustomDirectRouteProvider : DefaultDirectRouteProvider{    protected override string GetRoutePrefix(HttpControllerDescriptor controllerDescriptor)    {        var routePrefix =  base.GetRoutePrefix(controllerDescriptor);        var controllerBaseType = controllerDescriptor.ControllerType.BaseType;        if (controllerBaseType == typeof(BaseController))        {            //TODO: Check for extra slashes            routePrefix = "api/{tenantid}/" + routePrefix;        }        return routePrefix;    }}

Where BaseController is the one defining what is the prefix. Now normal prefixes work and you can add your own. When configuring routes, call

config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());


As @HazardouS identifies, @Grbinho's answer is hard-coded. Borrowing from this answer to inheritance of direct routing and from @HazardouS, I wrote this object

public class InheritableDirectRouteProvider : DefaultDirectRouteProvider {}

Then overrode the following methods, hoping RoutePrefixAttribute would get inherited:

protected override IReadOnlyList<IDirectRouteFactory> GetControllerRouteFactories(HttpControllerDescriptor controllerDescriptor){  // Inherit route attributes decorated on base class controller  // GOTCHA: RoutePrefixAttribute doesn't show up here, even though we were expecting it to.  //  Am keeping this here anyways, but am implementing an ugly fix by overriding GetRoutePrefix  return controllerDescriptor.GetCustomAttributes<IDirectRouteFactory>(true);}protected override IReadOnlyList<IDirectRouteFactory> GetActionRouteFactories(HttpActionDescriptor actionDescriptor){  // Inherit route attributes decorated on base class controller's actions  return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>(true);}

Sadly, per the gotcha comment, RoutePrefixAttribute doesn't show up in the factory list. I didn't dig into why, in case anyone wants to research a little deeper into this.So I kept those methods for future compatibility, and overrode the GetRoutePrefix method as follows:

protected override string GetRoutePrefix(HttpControllerDescriptor controllerDescriptor){  // Get the calling controller's route prefix  var routePrefix = base.GetRoutePrefix(controllerDescriptor);  // Iterate through each of the calling controller's base classes that inherit from HttpController  var baseControllerType = controllerDescriptor.ControllerType.BaseType;  while(typeof(IHttpController).IsAssignableFrom(baseControllerType))  {    // Get the base controller's route prefix, if it exists    // GOTCHA: There are two RoutePrefixAttributes... System.Web.Http.RoutePrefixAttribute and System.Web.Mvc.RoutePrefixAttribute!    //  Depending on your controller implementation, either one or the other might be used... checking against typeof(RoutePrefixAttribute)     //  without identifying which one will sometimes succeed, sometimes fail.    //  Since this implementation is generic, I'm handling both cases.  Preference would be to extend System.Web.Mvc and System.Web.Http    var baseRoutePrefix = Attribute.GetCustomAttribute(baseControllerType, typeof(System.Web.Http.RoutePrefixAttribute))       ?? Attribute.GetCustomAttribute(baseControllerType, typeof(System.Web.Mvc.RoutePrefixAttribute));    if (baseRoutePrefix != null)    {      // A trailing slash is added by the system. Only add it if we're prefixing an existing string      var trailingSlash = string.IsNullOrEmpty(routePrefix) ? "" : "/";      // Prepend the base controller's prefix      routePrefix = ((RoutePrefixAttribute)baseRoutePrefix).Prefix + trailingSlash + routePrefix;    }    // Traverse up the base hierarchy to check for all inherited prefixes    baseControllerType = baseControllerType.BaseType;  }  return routePrefix;}

Notes:

  1. Attribute.GetCustomAttributes(Assembly,Type,bool) methodincludes an "inherit" boolean... but it's ignored for this methodsignature. ARG! Because if it worked, we could have dropped thereflection loop... which takes us to the next point:
  2. This traverses up the inheritance hierarchy with reflection. Not idealbecause of the O(n) calls through reflection, but required for myneeds. You can get rid of the loop if you only have 1 or 2 levelsof inheritance.
  3. Per the GOTCHA in the code, RoutePrefixAttributeis declared in System.Web.Http and in System.Web.Mvc. They bothinherit directly from Attribute, and they both implement their ownIRoutePrefix interface (i.e.System.Web.Http.RoutePrefixAttribute<--System.Web.Http.IRoutePrefixandSystem.Web.Mvc.RoutePrefixAttribute<--System.Web.Mvc.IRoutePrefix).The end result is that the library used to declare your controller(web.mvc or web.http) is the library whose RoutePrefixAttribute isassigned. This makes sense, of course, but I lost 2 hoursrefactoring code that was actually legit because my test caseimplicitly checked for System.Web.Http.RoutePrefixAttribute but thecontroller was declared with System.Web.Mvc... Hence the explicit namespace in the code.


Tried this in ASP.NET Web Api 2.2 (should/might also work in MVC):

public class InheritedRoutePrefixDirectRouteProvider : DefaultDirectRouteProvider{    protected override string GetRoutePrefix(HttpControllerDescriptor controllerDescriptor)    {        var sb = new StringBuilder(base.GetRoutePrefix(controllerDescriptor));        var baseType = controllerDescriptor.ControllerType.BaseType;        for (var t = baseType; typeof(ApiController).IsAssignableFrom(t); t = t.BaseType)        {            var a = (t as MemberInfo).GetCustomAttribute<RoutePrefixAttribute>(false);            if (a != null)            {                sb.Insert(0, $"{a.Prefix}{(sb.Length > 0 ? "/": "")}");            }        }        return sb.ToString();    }}

It links the route prefixes together in the controller inheritance chain.