简体   繁体   English

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

[英]How do I validate the DI container in ASP.NET Core?

In my Startup class I use the ConfigureServices(IServiceCollection services) method to set up my service container, using the built-in DI container from Microsoft.Extensions.DependencyInjection .在我的Startup类中,我使用ConfigureServices(IServiceCollection services)方法来设置我的服务容器,使用来自Microsoft.Extensions.DependencyInjection的内置 DI 容器。

I want to validate the dependency graph in an unit test to check that all of the services can be constructed, so that I can fix any services missing during unit testing instead of having the app crash at runtime.我想在单元测试中验证依赖关系图以检查是否可以构建所有服务,以便我可以修复单元测试期间丢失的任何服务,而不是让应用程序在运行时崩溃。 In previous projects I've used Simple Injector, which has a .Verify() method for the container.在之前的项目中,我使用了 Simple Injector,它为容器提供了一个.Verify()方法。 But I haven't been able to find anything similar for ASP.NET Core.但是我一直找不到 ASP.NET Core 类似的东西。

Is there any built-in (or at least recommended) way of verifying that the entire dependency graph can be constructed?是否有任何内置(或至少推荐)方法来验证可以构建整个依赖图?

(The dumbest way I can think of is something like this, but it will still fail because of the open generics that are injected by the framework itself): (我能想到的最愚蠢的方法是这样的,但由于框架本身注入的开放泛型,它仍然会失败):

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

A built-in DI container validation was added in ASP.NET Core 3 and it is enabled only in the Development environment by default. ASP.NET Core 3 中添加了内置 DI 容器验证,默认情况下仅在Development环境中启用。 If something is missing, the container throws a fatal exception on startup.如果缺少某些东西,容器在启动时会抛出一个致命的异常。

Keep in mind that controllers aren't created in the DI container by default, so a typical web app won't get much from this check until the controllers are registered in the DI:请记住,默认情况下控制器不会在 DI 容器中创建,因此在控制器在 DI 中注册之前,典型的 Web 应用程序不会从该检查中获得太多信息:

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

To disable/customize the validation, add a IHostBuilder.UseDefaultServiceProvider call:要禁用/自定义验证,请添加IHostBuilder.UseDefaultServiceProvider调用:

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

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

To make sure your ASP.NET Core application works as expected and all dependencies are injected right, you should use Integration testing in ASP.NET Core . 要确保ASP.NET Core应用程序按预期工作并且所有依赖项都是正确注入的,您应该在ASP.NET Core中使用Integration测试 This way you can initialize TestServer with the same Startup class, so it causes all dependencies to be injected (no mocks or similar stubs) and test your application using the exposed routes/URLs/paths. 这样您就可以使用相同的Startup类初始化TestServer ,因此它会导致所有依赖项被注入(没有模拟或类似的存根),并使用公开的路由/ URL /路径测试您的应用程序。

Integration test for the default root URL might look like this: 默认根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);
    }
}

If you need to retrieve specific services injected via DI you cal always do it this way: 如果你需要检索通过DI注入的特定服务,你可以这样做:

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

Actually, I just used the example from your question with a few modifications and it worked pretty well for me.实际上,我只是使用了您问题中的示例并进行了一些修改,对我来说效果很好。 The theory behind filtering by classes in my namespace is that those will end up asking for everything else I care about.在我的命名空间中按类过滤背后的理论是,那些最终会要求我关心的其他一切。

My test looked a lot like this:我的测试看起来很像这样:

[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);
    }
}

I had same problem in one of my project.我在我的一个项目中遇到了同样的问题。 My resolve:我的决心:

  1. add methods like AddScopedService, AddTransientService and AddSingletonService, that add service to DI and then add it to some List.添加 AddScopedService、AddTransientService 和 AddSingletonService 等方法,将服务添加到 DI,然后将其添加到某个列表。 Use this methods instead of AddScoped, AddSingleton and AddTransient使用此方法代替 AddScoped、AddSingleton 和 AddTransient

  2. on startup application frist time i iterate by this list and call GetRequiredService.在第一次启动应用程序时,我遍历这个列表并调用 GetRequiredService。 If any service can't be resolved, application will not start如果任何服务无法解决,应用程序将不会启动

  3. I had CI: auto build and deploy on commit to develop branch.我有 CI: auto build and deploy on commit to develop 分支。 So if someone merge changes that broke DI, application fail and we all know about it.因此,如果有人合并破坏 DI 的更改,应用程序将失败,我们都知道。

  4. If u whant to do it faster, u can use TestServer in Dmitry Pavlov's answer with my solution together如果你想做得更快,你可以在 Dmitry Pavlov 的回答中使用 TestServer 和我的解决方案

Here is a unit test that you can add to your project:这是您可以添加到项目中的单元测试:

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using [X/N]Unit;

namespace MyProject.UnitTests
{
    public class DITests
    {
        [Fact or Test]
        public void AllServicesShouldConstructSuccessfully()
        {
            Host.CreateDefaultBuilder()
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder
                        .UseDefaultServiceProvider((context, options) =>
                        {
                            options.VailidateOnBuild = true;
                        })
                        .UseStartup<Startup>();
                }).Build();
        })
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM