在ASP.NET Core中发现通用控制器 [英] Discovering Generic Controllers in ASP.NET Core

查看:90
本文介绍了在ASP.NET Core中发现通用控制器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am trying to create a generic controller like this:

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

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

I intend for the {orderType} URI segment variable to control the generic type of the controller. I'm experimenting with both a custom IControllerFactory and IControllerActivator, but nothing is working. Every time I try to send a request, I get a 404 response. The code for my custom controller factory (and activator) is never executed.

显然,问题在于ASP.NET Core期望有效的控制器以后缀"Controller"结尾,但是我的通用控制器却具有(基于反射的)后缀"Controller`1".因此,它声明的基于属性的路由会被忽略.

Evidently the problem is that ASP.NET Core expects valid controllers to end with the suffix "Controller", but my generic controller instead has the (reflection based) suffix "Controller`1". Thus the attribute-based routes it declares are going unnoticed.

至少在早期,在ASP.NET MVC中, 负责发现所有可用的控制器.它测试了"Controller"后缀:

In ASP.NET MVC, at least in its early days, the DefaultControllerFactory was responsible for discovering all the available controllers. It tested for the "Controller" suffix:

MVC框架提供了一个默认的控制器工厂(适当地命名为DefaultControllerFactory),它将在appdomain中搜索所有程序集,以查找实现IController且名称以"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中,控制器工厂不再承担此责任.如前所述,我的自定义控制器工厂对普通"控制器执行,但从不为通用控制器调用.因此,在评估过程的早期,还有其他一些事情来控制控制器的发现.

Apparently, in ASP.NET Core, the controller factory no longer has this responsibility. As I stated earlier, my custom controller factory executes for "normal" controllers, but is never invoked for generic controllers. So there is something else, earlier in the evaluation process, which governs the discovery of controllers.

有人知道哪个服务"接口负责该发现吗?我不知道自定义界面或挂钩"点.

Does anyone know what "service" interface is responsible for that discovery? I don't know the customization interface or "hook" point.

有人知道让ASP.NET Core转储"它发现的所有控制器名称的方法吗?编写一个单元测试来验证我期望的任何自定义控制器发现确实可以正常工作将是非常棒的.

And does anyone know of a way to make ASP.NET Core "dump" the names of all the controllers it discovered? It would be great to write a unit test that verifies that any custom controller discovery I expect is indeed working.

顺便说一句,如果有一个钩子"允许发现通用控制器名称,则意味着路由替换也必须标准化:

Incidentally, if there is a "hook" which allows generic controller names to be discovered, it implies that route substitutions must also be normalized:

[Route("api/[controller]")]
public class OrdersController<T> : Controller { }

不管给T赋予什么值,[controller]名称都必须保持简单的基本通用名称.以上面的代码为例,[controller]的值为"Orders".不会是"Orders`1"或"OrdersOfSomething".

Regardless of what value for T is given, the [controller] name must remain a simple base-generic name. Using the above code as an example, the [controller] value would be "Orders". It would not be "Orders`1" or "OrdersOfSomething".

也可以通过显式声明封闭通用类型来解决此问题,而不是在运行时生成它们:

This problem could also be solved by explicitly declaring the closed-generic types, instead of generating them at run time:

public class VanityOrdersController : OrdersController<Vanity> { }
public class ExistingOrdersController : OrdersController<Existing> { }

以上方法有效,但是它会产生我不喜欢的URI路径:

The above works, but it produces URI paths that I don't like:

~/api/VanityOrders
~/api/ExistingOrders

我真正想要的是这个:

~/api/Orders/Vanity
~/api/Orders/Existing

另一种调整为我提供了我正在寻找的URI:

Another adjustment gets me the URI's I'm looking for:

[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才能查看"或发现"我的通用控制器,尽管其运行时反射名称并不以预期的"Controller"后缀结尾.

However, although this appears to work, it does not really answer my question. I would like to use my generic controller directly at run-time, rather than indirectly (via manual coding) at compile-time. Fundamentally, this means I need ASP.NET Core to be able to "see" or "discover" my generic controller, despite the fact that its run-time reflection name does not end with the expected "Controller" suffix.

推荐答案

简短回答

实现 IApplicationFeatureProvider<ControllerFeature> .

有人知道什么服务"界面负责[发现所有可用的控制器]吗?

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

ControllerFeatureProvider 对此负责.

The ControllerFeatureProvider is responsible for that.

有人知道让ASP.NET Core转储"它发现的所有控制器名称的方法吗?

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

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.

这是GitHub上的演示.祝你好运.

.NET版本

> dnvm install "1.0.0-rc2-20221" -runtime coreclr -architecture x64 -os win -unstable

NuGet.Config

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 CLI

> 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,因为 查看全文

登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆