【问题标题】:How do you inherit route prefixes at the controller class level in WebApi?如何在 WebApi 的控制器类级别继承路由前缀?
【发布时间】:2015-02-12 07:26:59
【问题描述】:

请注意,我已经阅读了作为 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 部分的情况下定义路由。

【问题讨论】:

  • 不是重复的。这个问题是询问实现基本控制器的控制器上的 RoutePrefix 。我问的是把 RoutePrefix 放在基本控制器上。请再读一遍。谢谢。

标签: c# asp.net inheritance asp.net-web-api


【解决方案1】:

我也有类似的要求。我所做的是:

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

【讨论】:

  • @kurifodo 为我工作。如果它也对你有用,请接受答案:)
  • 此解决方案在提供程序中硬编码所需的前缀。这是不可扩展的。 OP 想要基于继承的前缀。
  • if (controllerBaseType == typeof(BaseApiController)) { var baseRoutePrefixObject = controllerBaseType.CustomAttributes.FirstOrDefault(x =&gt; x.AttributeType == typeof(RoutePrefixAttribute))?.ConstructorArguments.FirstOrDefault().Value; String baseRoutePrefix = baseRoutePrefixObject as String; routePrefix = $"{baseRoutePrefix ?? String.Empty}/{routePrefix}"; } 用于排除硬编码
【解决方案2】:

正如@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);
}

遗憾的是,根据 gotcha 评论,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) 方法 包含一个“继承”布尔值...但此方法将其忽略 签名。啊!因为如果它有效,我们可以放弃 反射循环......这将我们带到下一点:
  2. 这会通过反射向上遍历继承层次结构。不理想 因为 O(n) 通过反射调用,但我需要 需要。如果您只有 1 或 2 个级别,您可以摆脱循环 的继承。
  3. 根据代码中的 GOTCHA,RoutePrefixAttribute 在 System.Web.Http 和 System.Web.Mvc 中声明。他们俩 直接继承自Attribute,它们都实现了自己的 IRoutePrefix 接口(即 System.Web.Http.RoutePrefixAttribute

【讨论】:

  • 这是一个很好的答案,唯一需要注意的是,如果 system.web.mvc 根本没有被引用怎么办?有没有办法让它更通用?
  • @JamesBlake 如果您没有在项目中包含 System.Web.Mvc 库,最简单的方法是剔除引用它的行。除此之外,您可以使用 this 之类的东西来反映加载了哪些程序集......但这太过分了 IMO
  • 为我工作。只需要添加 config.MapHttpAttributeRoutes(new InheritableDirectRouteProvider());到 WebApiConfig。
【解决方案3】:

在 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();
    }
}

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

【讨论】:

  • 你能给我举个例子吗?我已将您的代码复制到我的项目中,但它不起作用。我的控制器结构和问题的一样
【解决方案4】:

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

路由模板中的令牌替换([controller]、[action]、[area]) 为方便起见,属性路由通过将标记括在方括号 ([, ]) 中来支持标记替换。标记 [action]、[area] 和 [controller] 被替换为定义路由的操作中的操作名称、区域名称和控制器名称的值。在以下示例中,操作匹配 cmets 中描述的 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) { ... }
}

【讨论】:

    【解决方案5】:

    也许已经晚了,但我认为这个基本控制器属性会让它起作用:

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

    【讨论】:

    猜你喜欢
    • 2014-06-14
    • 1970-01-01
    • 1970-01-01
    • 2014-09-17
    • 1970-01-01
    • 1970-01-01
    • 2023-02-09
    • 2015-09-27
    • 1970-01-01
    相关资源
    最近更新 更多