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

查看:79
本文介绍了在 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 中,至少在其早期,DefaultControllerFactory 负责发现所有可用的控制器.它测试了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),它将搜索应用程序域中的所有程序集,寻找实现 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 才能看到"或发现"我的通用控制器,尽管它的运行时反射名称没有以预期的控制器"后缀结尾.

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.

推荐答案

简答

实施#Lreferr" rel="noref="代码>IApplicationFeatureProvider.

有谁知道什么服务"接口负责[发现所有可用的控制器]?

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

ControllerFeatureProvider 对此负责.

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

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

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 命令行界面

> 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 Core 中发现通用控制器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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