ASP.NET 'public' (Core) 的僵尸病毒:控制器可以是内部的吗?

ASP.NET (Core)'s zombie virus of 'public': can controllers be internal?

上下文是ASP.NET核心。

请考虑以下最小设置。

public class ExampleController : ControllerBase
{
   public ExampleController(IExempleService exampleService)
   {

(...)

public interface IExampleService
{
   ExampleStruct Foo();
}

public readonly struct ExampleStruct (...)
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IInverterService, InverterService>();

这里有一个 publicity 链。

  1. 控制器必须是public;被发现。 一般。
  2. 控制器的构造函数必须是public;让框架实例化它。 这就是生活。
  3. 服务接口必须是public,因为控制器的构造函数是public。 悲伤。
  4. 服务类型(例如 ExampleStruct)必须是 public,因为接口是 public。 我觉得恶心。

这样做的实际后果是:

我想这里有两个问题:


单元测试和 internalInternalsVisibleToAttribute 相结合。

所有功劳应归功于@MichaelRandall:

You can make your controllers internal, you will just have to make them discoverable with ControllerFeatureProvider take a look at this whosebug.com/a/50906593/1612975 – Michael Randall


public void ConfigureServices(IServiceCollection services)
{
    (...)
    services
        .AddControllers(options => ...)
        .ConfigureApplicationPartManager(manager =>
        {
            manager.FeatureProviders.Add(new MyControllerFeatureProvider());
        })
     
(...)   

internal class MyControllerFeatureProvider : ControllerFeatureProvider
{
    protected override bool IsController(TypeInfo typeInfo)
    {
        // https://source.dot.net/#Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeatureProvider.cs,41
        // contains
        // 
        // // We only consider public top-level classes as controllers. IsPublic returns false for nested
        // // classes, regardless of visibility modifiers
        // if (!typeInfo.IsPublic)
        // {
        //   return false;
        // }

        if (typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) &&
            typeInfo.IsDefined(typeof(ApiControllerAttribute)))
        {
            System.Diagnostics.Debug.WriteLine($"Found controller '{typeInfo.Name}'");
            return true;
        }

        if (typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
        {
            System.Diagnostics.Debug.Fail($"Found class that ends in 'Controller' but will not be registered as a controller: {typeInfo.Name}");
        }

        return false;
    }
}

可以制作你的控制器internal,你将拥有使它们 可发现 ControllerFeatureProvider

Discovers controllers from a list of ApplicationPart instances.

在 Asp.Net Core 中有 4 种定义控制器的方式:

  1. 继承自ControllerBaseclass
  2. 定义一个名称包含“Controller”后缀的控制器。
  3. 使用控制器属性定义
  4. 定义自定义控制器类型Class

对您的 internal 控制器情况最重要的是最后一点(定义自定义控制器类型)。

要做到这一点,您需要

  • 实施 ControllerFeatureProvider
  • 覆盖 IsController 并实施任何逻辑以提高您的类型的可发现性

例如

internal class MyControllerFeatureProvider : ControllerFeatureProvider
{
    protected override bool IsController(TypeInfo typeInfo)
    {
        ...
  • 通过 FeatureProviders
  • 将其添加到 ConfigureApplicationPartManager

例如

.ConfigureApplicationPartManager(manager =>
{
    manager.FeatureProviders.Add(YourControllerFeatureProvider)
    ...