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

时间:2022-09-25 07:37:46

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 to solve the issue of inheriting action level route attributes, but not route prefixes defined at the class level. http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-22#ARI

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

I would like to do something like this:

我想做这样的事情:

[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() { ... }
}

I would like the account route prefix to be inherited, so when defining the Facebook and Google controllers, I get routes:

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

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

Currently, routes are getting defined without the account portion from the base class.

目前,在没有基类的帐户部分的情况下定义路由。

3 个解决方案

#1


13  

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

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

config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());

#2


4  

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

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

public class InheritableDirectRouteProvider : DefaultDirectRouteProvider {}

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

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

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:

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

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. Attribute.GetCustomAttributes(Assembly,Type,bool)方法包含一个“inherit”布尔值......但是这个方法签名被忽略了。 ARG!因为如果它起作用,我们可以放弃反射循环......这将我们带到下一点:
  3. 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.
  4. 这会使用反射遍历继承层次结构。由于O(n)通过反射调用,但不是理想的,但需要我的需要。如果你只有1或2级继承,你可以摆脱循环。
  5. 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.
  6. 根据代码中的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声明的...因此显式名称空间在代码中。

#3


0  

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

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

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

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

#1


13  

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

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

config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());

#2


4  

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

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

public class InheritableDirectRouteProvider : DefaultDirectRouteProvider {}

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

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

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:

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

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. Attribute.GetCustomAttributes(Assembly,Type,bool)方法包含一个“inherit”布尔值......但是这个方法签名被忽略了。 ARG!因为如果它起作用,我们可以放弃反射循环......这将我们带到下一点:
  3. 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.
  4. 这会使用反射遍历继承层次结构。由于O(n)通过反射调用,但不是理想的,但需要我的需要。如果你只有1或2级继承,你可以摆脱循环。
  5. 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.
  6. 根据代码中的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声明的...因此显式名称空间在代码中。

#3


0  

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

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

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

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