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

后端 未结 5 1446
粉色の甜心
粉色の甜心 2021-01-11 10:49

Note, I\'ve read about the new routing features as part of WebApi 2.2 to allow for inheritance of routes. This does not seem to solve my particular issue, however. It seems

5条回答
  •  忘掉有多难
    2021-01-11 11:43

    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 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(true);
    }
    
    protected override IReadOnlyList GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
    {
      // Inherit route attributes decorated on base class controller's actions
      return actionDescriptor.GetCustomAttributes(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) method includes an "inherit" boolean... but it's ignored for this method signature. ARG! Because if it worked, we could have dropped the reflection loop... which takes us to the next point:
    2. This traverses up the inheritance hierarchy with reflection. Not ideal because of the O(n) calls through reflection, but required for my needs. You can get rid of the loop if you only have 1 or 2 levels of inheritance.
    3. Per the GOTCHA in the code, RoutePrefixAttribute is declared in System.Web.Http and in System.Web.Mvc. They both inherit directly from Attribute, and they both implement their own IRoutePrefix interface (i.e. System.Web.Http.RoutePrefixAttribute<--System.Web.Http.IRoutePrefix and System.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 is assigned. This makes sense, of course, but I lost 2 hours refactoring code that was actually legit because my test case implicitly checked for System.Web.Http.RoutePrefixAttribute but the controller was declared with System.Web.Mvc... Hence the explicit namespace in the code.

提交回复
热议问题