【发布时间】:2016-08-09 10:06:06
【问题描述】:
我正在尝试创建一个像这样的通用控制器:
[Route("api/[controller]")]
public class OrdersController<T> : Controller where T : IOrder
{
[HttpPost("{orderType}")]
public async Task<IActionResult> Create(
[FromBody] Order<T> order)
{
//....
}
}
我打算用 {orderType} URI 段变量来控制控制器的通用类型。我正在尝试自定义IControllerFactory 和IControllerActivator,但没有任何效果。每次我尝试发送请求时,都会收到 404 响应。我的自定义控制器工厂(和激活器)的代码永远不会执行。
显然,问题在于 ASP.NET Core 期望有效的控制器以后缀“Controller”结尾,但我的通用控制器却具有(基于反射的)后缀“Controller`1”。因此,它声明的基于属性的路由不会被注意到。
在 ASP.NET MVC 中,至少在早期,the DefaultControllerFactory was responsible for discovering all the available controllers。它测试了“控制器”后缀:
MVC 框架提供了一个默认控制器工厂(恰当地命名为 DefaultControllerFactory),它将搜索应用程序域中的所有程序集,以查找所有实现 IController 并且名称以“Controller”结尾的类型。
显然,在 ASP.NET Core 中,控制器工厂不再有这个责任。正如我之前所说,我的自定义控制器工厂为“普通”控制器执行,但从不为通用控制器调用。因此,在评估过程的早期,还有其他一些东西控制着控制器的发现。
有谁知道负责该发现的“服务”接口是什么?我不知道自定义界面或“钩子”点。
有没有人知道一种方法可以让 ASP.NET Core “转储”它发现的所有控制器的名称?编写一个单元测试来验证我期望的任何自定义控制器发现确实有效,这将是很棒的。
顺便说一句,如果存在允许发现通用控制器名称的“钩子”,则意味着路由替换也必须被规范化:
[Route("api/[controller]")]
public class OrdersController<T> : Controller { }
无论T 的值是什么,[控制器] 名称都必须是一个简单的基本通用名称。以上面的代码为例,[controller] 值将是“Orders”。它不会是“Orders`1”或“OrdersOfSomething”。
注意
这个问题也可以通过显式声明封闭的泛型类型来解决,而不是在运行时生成它们:
public class VanityOrdersController : OrdersController<Vanity> { }
public class ExistingOrdersController : OrdersController<Existing> { }
上述方法有效,但它会产生我不喜欢的 URI 路径:
~/api/VanityOrders
~/api/ExistingOrders
我真正想要的是这样的:
~/api/Orders/Vanity
~/api/Orders/Existing
另一个调整让我得到我正在寻找的 URI:
[Route("api/Orders/Vanity", Name ="VanityLink")]
public class VanityOrdersController : OrdersController<Vanity> { }
[Route("api/Orders/Existing", Name = "ExistingLink")]
public class ExistingOrdersController : OrdersController<Existing> { }
但是,尽管这似乎可行,但并不能真正回答我的问题。我想在运行时直接使用我的通用控制器,而不是在编译时间接(通过手动编码)。从根本上说,这意味着我需要 ASP.NET Core 能够“看到”或“发现”我的通用控制器,尽管它的运行时反射名称不以预期的“控制器”后缀结尾。
【问题讨论】:
标签: asp.net-mvc asp.net-web-api asp.net-core url-routing