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

时间:2014-12-13 20:03:35

标签: 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() { ... }
}

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

~/account/facebook/foo
~/account/google/bar

目前,路由的定义没有基类的account部分。

5 个答案:

答案 0 :(得分: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;
    }
}

其中BaseController是定义前缀的那个。现在普通的前缀工作,您可以添加自己的前缀。配置路由时,请调用

config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());

答案 1 :(得分:5)

正如@HazardouS所说,@ Grbinho的答案是硬编码的。借用this answer to inheritance of direct routing和@HazardouS,我写了这个对象

public class InheritableDirectRouteProvider : DefaultDirectRouteProvider {}

然后覆盖以下方法,希望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);
}

可悲的是,根据问题评论,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;
}

注意:

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

答案 2 :(得分:0)

在ASP.NET Web Api 2.2中尝试过这种方法(应该/可能也适用于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();
    }
}

它在控制器继承链中将路由前缀链接在一起。

答案 3 :(得分:0)

也许已经晚了,但我认为此基本控制器属性将使其起作用:

[Route("account/[Controller]")]

答案 4 :(得分:0)

我只是在.NET Core 3.0应用程序中遇到了同样的问题(似乎是MVC 6中的一项新功能,因此它不适用于MVC 5和以前的版本,但对偶然发现的其他人可能仍然有帮助这个问题)。我没有足够的代表就@EmilioRojo的答案发表评论,但他是正确的。这是Microsoft Docs提供的更多信息,可帮助遇到同一问题的人们。

路由模板([控制器],[操作],[区域])中的令牌替换 为方便起见,属性路由通过将令牌括在方括号([,])中来支持令牌替换。标记[action],[area]和[controller]被定义路径的动作中的动作名称,区域名称和控制器名称的值替换。在以下示例中,操作与注释中所述的URL路径匹配:

[Route("[controller]/[action]")]
public class ProductsController : Controller
{
    [HttpGet] // Matches '/Products/List'
    public IActionResult List() {
        // ...
    }

    [HttpGet("{id}")] // Matches '/Products/Edit/{id}'
    public IActionResult Edit(int id) {
        // ...
    }
}

属性路由也可以与继承结合使用。 与令牌替换结合使用,功能特别强大。

[Route("api/[controller]")]
public abstract class MyBaseController : Controller { ... }

public class ProductsController : MyBaseController
{
   [HttpGet] // Matches '/api/Products'
   public IActionResult List() { ... }

   [HttpPut("{id}")] // Matches '/api/Products/{id}'
   public IActionResult Edit(int id) { ... }
}