如何在WebApi中继承控制器类级别的路由前缀?

Sco*_*Lin 13 c# asp.net inheritance asp.net-web-api

注意,我已经阅读了新的路由功能作为WebApi 2.2的一部分,以允许继承路由.然而,这似乎并没有解决我的特定问题.它似乎解决了继承动作级别路由属性的问题,但没有解决在类级别定义的路由前缀. http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-22#ARI

我想做这样的事情:

[RoutePrefix("account")]
public abstract class AccountControllerBase : ControllerBase { }

[RoutePrefix("facebook")]
public class FacebookController : AccountControllerBase
{
    [Route("foo")]
    public async Task<string> GetAsync() { ... }
}

[RoutePrefix("google")]
public class GoogleController : AccountControllerBase
{
    [Route("bar")]
    public async Task<string> GetAsync() { ... }
}
Run Code Online (Sandbox Code Playgroud)

我希望account继承路由前缀,因此在定义Facebook和Google控制器时,我会获得路由:

~/account/facebook/foo
~/account/google/bar
Run Code Online (Sandbox Code Playgroud)

目前,在没有account基类部分的情况下定义路由.

Grb*_*nho 13

我有类似的要求.我做的是:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

BaseController一个定义什么是前缀.现在普通的前缀工作,您可以添加自己的前缀.配置路由时,请致电

config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());
Run Code Online (Sandbox Code Playgroud)

  • 该解决方案在提供商内硬编码所需的前缀.这不可扩展.OP需要基于继承的前缀. (3认同)

Hey*_*iko 5

正如@HazardouS所说,@ Grbinho的答案是硬编码的.借用这个直接路由继承的答案和@HazardouS,我写了这个对象

public class InheritableDirectRouteProvider : DefaultDirectRouteProvider {}
Run Code Online (Sandbox Code Playgroud)

然后覆盖以下方法,希望RoutePrefixAttribute继承:

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);
}
Run Code Online (Sandbox Code Playgroud)

遗憾的是,根据问题评论,RoutePrefixAttribute不会显示在工厂列表中.我没有深入研究为什么,如果有人想要深入研究这一点.所以我保留了这些方法以备将来兼容,并覆盖了GetRoutePrefix方法,如下所示:

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;
}
Run Code Online (Sandbox Code Playgroud)

笔记:

  1. Attribute.GetCustomAttributes(Assembly,Type,bool)方法包含一个"inherit"布尔值......但是这个方法签名被忽略了.ARG!因为如果它有效,我们可以放弃反射循环......这将我们带到下一点:
  2. 这会使用反射遍历继承层次结构.由于O(n)通过反射调用,但不是理想的,但需要我的需求.如果你只有1或2级继承,你可以摆脱循环.
  3. 根据代码中的GOTCHA,RoutePrefixAttribute在System.Web.Http和System.Web.Mvc中声明.它们都直接从Attribute继承,并且它们都实现了自己的IRoutePrefix接口(即System.Web.Http.RoutePrefixAttribute < - System.Web.Http.IRoutePrefix和System.Web.Mvc.RoutePrefixAttribute < - System.Web.Mvc .IRoutePrefix).最终结果是用于声明控制器的库(web.mvc或web.http)是分配了RoutePrefixAttribute的库.当然,这是有道理的,但是我失去了2小时的重构代码,这实际上是合法的,因为我的测试用例隐式检查了System.Web.Http.RoutePrefixAttribute但是控制器是用System.Web.Mvc声明的...因此显式命名空间在代码中.