如何在 WebAPI 中的控制器类级别继承路由前缀
本文关键字:继承 路由 前缀 控制器 WebAPI | 更新日期: 2023-09-27 18:34:11
注意,我已经阅读了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
部分。
我也有类似的要求。我所做的是:
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());
正如@HazardouS所识别的那样,@Grbinho的答案是硬编码的。 借用这个直接路由继承的答案和@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;
}
笔记:
- Attribute.GetCustomAttributes(Assembly,Type,bool( 方法包括一个"继承"布尔值...但此方法忽略了它签名。精 氨 酸!因为如果它有效,我们可以放弃反射循环...这将我们带到下一点:
- 这将通过反射向上遍历继承层次结构。不理想因为通过反射的 O(n( 调用,但对于我的需要。 如果您只有 1 或 2 个级别,则可以摆脱循环的继承。
- 根据代码中的陷阱,路由前缀属性在 System.Web.Http 和 System.Web.Mvc 中声明。他们都直接从属性继承,它们都实现了自己的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...因此,代码中的显式命名空间。
在 Web Api 2.2 中尝试过 ASP.NET(应该/也可能在 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();
}
}
它将控制器继承链中的路由前缀链接在一起。
我刚刚在 .NET Core 3.0 应用程序上遇到了同样的问题(似乎是 MVC 6 中的一个新功能,因此它不适用于 MVC 5 和以前的版本,但可能仍然对偶然发现此问题的任何其他人有帮助(。 我没有足够的代表对@EmilioRojo的回答发表评论,但他是正确的。 以下是来自 Microsoft 文档的更多信息,可帮助遇到相同问题的用户。
路由模板中的令牌替换([控制器]、[操作]、[区域](为方便起见,属性路由通过将标记括在方括号 ([, ]( 中来支持标记替换。标记 [操作]、[区域] 和 [控制器] 将替换为定义路由的操作中的操作名称、区域名称和控制器名称的值。在以下示例中,操作与注释中所述的 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) { ... }
}
也许晚了,但我认为这个基本控制器属性会让它工作:
[Route("account/[Controller]")]