【问题标题】:Why ServiceProvider is unable to create instance of controller in ASP.NET Core?为什么 ServiceProvider 无法在 ASP.NET Core 中创建控制器实例?
【发布时间】:2017-07-02 13:14:55
【问题描述】:

我在 .NET Core 中开发集成测试。因为控制器有多个依赖项,手动创建它们的实例很不方便。以下是我创建 ServiceProvider 实例以访问 .NET Core 原生 DI 容器的方法:

HostingEnvironment env = new HostingEnvironment();
env.ContentRootPath = Directory.GetCurrentDirectory();
env.EnvironmentName = "Development";

Startup startup = new Startup(env);
ServiceCollection sc = new ServiceCollection();
startup.ConfigureServices(sc);
ServiceProvider = sc.BuildServiceProvider();

Startup 类取自测试程序集。

令我惊讶的是,服务提供商无法创建控制器实例。它可以实例化注册的服务,但不能实例化控制器。有人知道为什么会这样吗?

【问题讨论】:

标签: dependency-injection asp.net-core asp.net-core-mvc


【解决方案1】:

您应该使用TestServer,它引导一个完整的测试环境(可以选择使用不同的启动类或环境变量,您可以在其中将数据存储替换为内存存储)。

ASP.NET Core documentation 很好地涵盖了这种情况。

var server = new TestServer(new WebHostBuilder()
    .UseStartup<Startup>()
    // this would cause it to use StartupIntegrationTest or ConfigureServicesIntegrationTest / ConfigureIntegrationTest methods (if existing)
    // rather than Startup, ConfigureServices and Configure
    .UseEnvironment("IntegrationTest"));

在这些替代/环境相关的方法中,您可以放置​​您的集成测试特定的替换/DI 配置。

其次,控制器(以及标签助手和视图组件)默认未在 DI 系统中注册。控制器工厂将解析类型。如果您希望通过内置或第 3 方 IoC 容器解析控制器,则必须告诉 ASP.NET Core 显式注册它们(请参阅this related question)。

这是一个例子:

services
    .AddMvc()
    .AddControllersAsServices()
    .AddViewComponentsAsServices()
    .AddTagHelpersAsServices();

我建议您使用第一种方法,因为它更一致,并且正是具有环境支持的 Startup 类存在的原因。

更新

重要补充。如果你使用TestServer,你也可以访问它的DI Container(IServiceProvider实例)。

var server = new TestServer(new WebHostBuilder()
        .UseStartup<Startup>()
        .UseEnvironment("IntegrationTest"));
var controller = server.Host.Services.GetService<MyController>();

【讨论】:

    【解决方案2】:

    由于控制器有多个依赖项,手动创建它们的实例很不方便。

    情况不应该如此。编写单元测试时,不应该依赖 DI 容器;您应该手动创建依赖项。添加对容器的依赖只会使您的测试复杂化并添加对库的不需要的依赖。

    您可以应用一些有趣的模式来更轻松地创建SUT。其中之一是Test Data Builder 模式的变体,您在测试类中有一个私有工厂方法来构建 SUT:

    private static ShipOrderController CreateController(
        IShipOrderHandler handler = null,
        ILogger logger = null,
        IMailSender mailSender = null)
    {
        return new ShipOrderController(
            handler ?? new ShipOrderHandlerStub(),
            logger ?? new LoggerStub(),
            mailSender ?? new MailSenderStub());
    }
    

    这样的 SUT Builder 使您的单元测试非常可读和可维护,因为向 SUT 添加依赖项只会影响工厂方法;没有任何现有的测试。

    测试可以如下所示:

    [TestMethod]
    public void Index_Always_Logs()
    {
        // Arrange
        var logger = new FakeLogger();
    
        var sut = CreateController(logger: logger);
    
        // Act
        sut.Index();
    
        // Assert
        Assert.IsTrue(logger.Entries.Any());
    }
    

    【讨论】:

    • 在编写单元测试时,不应该依赖 DI Container;您应该手动创建依赖项重新阅读问题;)他询问集成测试
    • @Tseng:感谢您指出这一点。你是对的,对于集成测试,使用与生产环境相同的基础设施(即容器)更自然。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-31
    • 1970-01-01
    • 2022-01-27
    • 1970-01-01
    • 2016-05-22
    • 1970-01-01
    • 2014-07-11
    相关资源
    最近更新 更多