如何在ASP.NET Core中验证DI容器?

时间:2018-03-07 10:03:19

标签: c# asp.net-core dependency-injection

在我的Startup课程中,我使用ConfigureServices(IServiceCollection services)方法设置我的服务容器,使用Microsoft.Extensions.DependencyInjection中的内置DI容器。

我想在单元测试中验证依赖图,以检查是否可以构造所有服务,这样我就可以修复在单元测试期间丢失的任何服务,而不是让应用程序在运行时崩溃。在以前的项目中,我使用过Simple Injector,它对容器有一个.Verify()方法。但我还没能找到类似ASP.NET Core的东西。

是否有任何内置(或至少推荐)的方式来验证是否可以构建整个依赖图?

(我能想到的最愚蠢的方式就是这样,但由于框架本身注入的开放式泛型,它仍然会失败):

startup.ConfigureServices(serviceCollection);
var provider = serviceCollection.BuildServiceProvider();
foreach (var serviceDescriptor in serviceCollection)
{
    provider.GetService(serviceDescriptor.ServiceType);
}

4 个答案:

答案 0 :(得分:9)

内置的DI容器验证已添加到ASP.NET Core 3中,并且默认情况下仅在Development环境中启用。如果缺少某些内容,则容器在启动时会引发致命异常。

请记住,默认情况下不会在DI容器中创建控制器,因此,典型的Web应用程序在此检查不会获得很多收益,除非将控制器注册到DI中:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers()
        .AddControllersAsServices();
}

要禁用/自定义验证,请添加一个IHostBuilder.UseDefaultServiceProvider调用:

public class Program
{
    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            //...
            .UseDefaultServiceProvider((context, options) =>
            {
                options.ValidateOnBuild = false;
            });

此验证功能有几个限制,请在此处阅读更多信息:https://andrewlock.net/new-in-asp-net-core-3-service-provider-validation/

答案 1 :(得分:1)

实际上,我只是使用您问题中的示例进行了一些修改,对我来说效果很好。在我的命名空间中按类进行过滤的理论是,这些过滤器最终会询问我关心的所有其他问题。

我的测试看起来像这样:

[Test or Fact or Whatever]
public void AllDependenciesPresentAndAccountedFor()
{
    // Arrange
    var startup = new Startup();

    // Act
    startup.ConfigureServices(serviceCollection);

    // Assert
    var exceptions = new List<InvalidOperationException>();
    var provider = serviceCollection.BuildServiceProvider();
    foreach (var serviceDescriptor in services)
    {
        var serviceType = serviceDescriptor.ServiceType;
        if (serviceType.Namespace.StartsWith("my.namespace.here"))
        {
            try
            {
                provider.GetService(serviceType);
            }
            catch (InvalidOperationException e)
            {
                exceptions.Add(e);
            }
        }
    }

    if (exceptions.Any())
    {
        throw new AggregateException("Some services are missing", exceptions);
    }
}

答案 2 :(得分:0)

要确保ASP.NET Core应用程序按预期工作并且所有依赖项都是正确注入的,您应该使用Integration testing in ASP.NET Core。这样,您可以使用相同的TestServer类初始化Startup,因此它会导致所有依赖项被注入(没有模拟或类似的存根),并使用公开的路由/ URL /路径测试您的应用程序。

默认根URL的集成测试可能如下所示:

public class PrimeWebDefaultRequestShould
{
    private readonly TestServer _server;
    private readonly HttpClient _client;
    public PrimeWebDefaultRequestShould()
    {
        // Arrange
        _server = new TestServer(new WebHostBuilder().UseStartup<Startup>());
        _client = _server.CreateClient();
    }

    [Fact]
    public async Task ReturnHelloWorld()
    {
        // Act
        var response = await _client.GetAsync("/");
        response.EnsureSuccessStatusCode();

        var responseString = await response.Content.ReadAsStringAsync();

        // Assert
        Assert.Equal("Hello World!", responseString);
    }
}

如果你需要检索通过DI注入的特定服务,你可以这样做:

var service = _server.Host.Services.GetService(typeof(IYourService));

答案 3 :(得分:0)

我在一个项目中遇到了同样的问题。我的决心:

  1. 添加诸如AddScopedService,AddTransientService和AddSingletonService之类的方法,这些方法将服务添加到DI,然后将其添加到某些List。使用此方法代替AddScoped,AddSingleton和AddTransient

  2. 在启动应用程序的第一时间,按
  3. 循环访问此列表并调用GetRequiredService。如果无法解决任何服务,则应用程序将无法启动

  4. 我有CI:提交后自动构建并部署到开发分支。因此,如果有人合并破坏了DI的更改,则应用程序将失败,我们都知道。

  5. 如果希望更快地执行此操作,则可以将Dmitry Pavlov的答案中的TestServer与我的解决方案一起使用