asp.net-mvc - 在 ASP.NET Core 中发现通用 Controller

标签 asp.net-mvc asp.net-web-api asp.net-core url-routing

我正在尝试创建一个像这样的通用 Controller :

[Route("api/[controller]")]
public class OrdersController<T> : Controller where T : IOrder
{
    [HttpPost("{orderType}")]
    public async Task<IActionResult> Create(
        [FromBody] Order<T> order)
    {
       //....
    }
}

我打算让 {orderType} URI 段变量来控制 Controller 的通用类型。我正在尝试自定义 IControllerFactoryIControllerActivator ,但没有任何工作。每次我尝试发送请求时,都会收到 404 响应。我的自定义 Controller 工厂(和激活器)的代码永远不会执行。

显然,问题在于 ASP.NET Core 期望有效的 Controller 以后缀“Controller”结尾,但我的通用 Controller 却具有(基于反射的)后缀“Controller`1”。因此,它声明的基于属性的路由不会被注意到。

在 ASP.NET MVC 中,至少在早期,the DefaultControllerFactory was responsible for discovering all the available controllers .它测试了“ Controller ”后缀:

The MVC framework provides a default controller factory (aptly named DefaultControllerFactory) that will search through all the assemblies in an appdomain looking for all types that implement IController and whose name ends with "Controller."



显然,在 ASP.NET Core 中, Controller 工厂不再有这个责任。正如我之前所说,我的自定义 Controller 工厂为“普通” Controller 执行,但从不为通用 Controller 调用。因此,在评估过程的早期,还有其他一些东西控制着 Controller 的发现。

有谁知道负责该发现的“服务”接口(interface)是什么?我不知道自定义界面或“钩子(Hook)”点。

有没有人知道一种方法可以让 ASP.NET Core “转储”它发现的所有 Controller 的名称?编写一个单元测试来验证我期望的任何自定义 Controller 发现确实有效,这将是很棒的。

顺便说一句,如果有一个允许发现通用 Controller 名称的“钩子(Hook)”,则意味着路由替换也必须规范化:
[Route("api/[controller]")]
public class OrdersController<T> : Controller { }

不管 T 的值是多少给出,[ Controller ]名称必须保持一个简单的基本通用名称。以上面的代码为例,[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> { }

但是,尽管这似乎可行,但并不能真正回答我的问题。我想在运行时直接使用我的通用 Controller ,而不是在编译时间接(通过手动编码)。从根本上说,这意味着我需要 ASP.NET Core 能够“看到”或“发现”我的通用 Controller ,尽管它的运行时反射名称没有以预期的“ Controller ”后缀结尾。

最佳答案

简答

实现 IApplicationFeatureProvider<ControllerFeature> .

问题和答案

Does anyone know what "service" interface is responsible for [discovering all available controllers]?



ControllerFeatureProvider 对此负责。

And does anyone know of a way to make ASP.NET Core "dump" the names of all the controllers it discovered?



ControllerFeatureProvider.IsController(TypeInfo typeInfo) 内执行此操作.

例子

MyControllerFeatureProvider.cs
using System;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Controllers;

namespace CustomControllerNames 
{
    public class MyControllerFeatureProvider : ControllerFeatureProvider 
    {
        protected override bool IsController(TypeInfo typeInfo)
        {
            var isController = base.IsController(typeInfo);

            if (!isController)
            {
                string[] validEndings = new[] { "Foobar", "Controller`1" };

                isController = validEndings.Any(x => 
                    typeInfo.Name.EndsWith(x, StringComparison.OrdinalIgnoreCase));
            }

            Console.WriteLine($"{typeInfo.Name} IsController: {isController}.");

            return isController;
        }
    }
}

在启动时注册它。
public void ConfigureServices(IServiceCollection services)
{
    services
        .AddMvcCore()
        .ConfigureApplicationPartManager(manager => 
        {
            manager.FeatureProviders.Add(new MyControllerFeatureProvider());
        });
}

这是一些示例输出。
MyControllerFeatureProvider IsController: False.
OrdersFoobar IsController: True.
OrdersFoobarController`1 IsController: True.
Program IsController: False.
<>c__DisplayClass0_0 IsController: False.
<>c IsController: False.

And here is a demo on GitHub .祝你好运。

编辑 - 添加版本

.NET 版本
> dnvm install "1.0.0-rc2-20221" -runtime coreclr -architecture x64 -os win -unstable

NuGet.Config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <clear/>
    <add key="AspNetCore" 
         value="https://www.myget.org/F/aspnetvnext/api/v3/index.json" />  
  </packageSources>
</configuration>

.NET 命令行
> dotnet --info
.NET Command Line Tools (1.0.0-rc2-002429)

Product Information:
 Version:     1.0.0-rc2-002429
 Commit Sha:  612088cfa8

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.10586
 OS Platform: Windows
 RID:         win10-x64

恢复、构建和运行
> dotnet restore
> dotnet build
> dotnet run

编辑 - 关于 RC1 与 RC2 的注释

这可能是 RC1,因为 DefaultControllerTypeProvider.IsController() 标记为 internal .

关于asp.net-mvc - 在 ASP.NET Core 中发现通用 Controller ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36680933/

相关文章:

c# - 如何为 Html.TextBoxFor( ... ) 文本框设置默认值?

c# - Asp.net 成员(member)提供程序在 url 中显示登录信息

javascript - 将选定的值数组发送到 Controller - MVC

asp.net-mvc - 无法从 Models.<entityviewmodel> 转换为 Models.<entity>

wcf - 在(每个)Web API 操作之前执行代码

c# - SQL 写入 ASP.NET 用户表不保存

.net - 覆盖 MVC POST 请求的内容 header

entity-framework - 如何使用 Audit.NET Entity Framework Data Provider 保存 Audit.NET WebAPI 审计日志?

asp.net-core - Jetbrains 骑士 : [MSB4057] The target "build" does not exist in the project

c# - .NET Standard 2.0项目中为什么可以添加.NET framework项目作为引用?