具有相同 DI 容器的多个主机

GTH*_*ten 5 c# dependency-injection .net-core

在 C#.NET Core 中,您可以使用以下代码创建通用主机:

IHostBuilder builder = new HostBuilder()
    .ConfigureServices((context, collection) => {
        collection.AddSingleton<IMyClass, MyClass>();
        collection.AddHostedService<MyService>();
    });
await builder.RunConsoleAsync();
Run Code Online (Sandbox Code Playgroud)

MyService这将使用默认 DI 容器创建一个新实例。

现在,假设我想在 内部创建一个新主机MyService。这很简单(在本例中是网络主机):

IWebHost webHost = WebHost.CreateDefaultBuilder()
    .UseStartup<MyStartup>()
    .Build();
    .RunAsync();
Run Code Online (Sandbox Code Playgroud)

该 Web 主机将拥有自己的依赖项注入容器,因此它将无法访问我已添加到通用主机容器中的所有依赖项:即,它将无法注入IMyClassMyStartup.

我还尝试IServiceProviderFactory<>使用以下代码添加自定义(基于.UseDefaultServiceProvider()它们用作IServiceCollection构建器类型的代码):

public class CustomServiceProviderFactory :  IServiceProviderFactory<IServiceCollection>
{
    private readonly IServiceProvider _provider;

    public CustomServiceProviderFactory(IServiceProvider provider)
    {
        _provider = provider;
    }

    public IServiceCollection CreateBuilder(IServiceCollection services)
    {
        return services;
    }

    public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder)
    {
        return _provider;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在我的中,HostBuilder我通过添加了它.UseServiceProviderFactory(new CustomServiceProviderFactory(_serviceProvider)),但由于某种原因,HostedService在创建它之前实例化了它,导致 DI 异常关于找不到所需的对象。

然而,现在看来 asWebHost.CreateDefaultBuilder()是创建 Web 主机的首选方式(在 .NET Core 3.0 中),并且IWebHostBuilder没有设置自定义的选项IServiceProviderFactory,这似乎是一个死胡同。

如何让 Web 主机使用与初始通用主机相同的 DI 容器?

Eza*_*Eza 1

我也尝试过做同样的事情,这就是我所实现的。尚未完全测试,但它似乎确实有效。

首先,在我的 base/first 中HostBuilder,将服务集合添加为服务,以便IServiceCollection稍后可以通过 DI 进行解析。

IHostBuilder builder = new HostBuilder()
    .ConfigureServices((ctx, services) =>
    {
        services.AddSingleton<IMyService, MyService>();
        services.AddHostedService<MyApp>();
        services.AddSingleton(services);
    });
Run Code Online (Sandbox Code Playgroud)

IHostedService.StartAsync()我创建 WebHost. services.Replace我从里面的功能复制了使用UseDefaultServiceProvider()

IWebHost host = WebHost
            .CreateDefaultBuilder()
            .ConfigureServices(services =>
            {
                var options = new ServiceProviderOptions();               
                services.Replace(ServiceDescriptor.Singleton<IServiceProviderFactory<IServiceCollection>>(new CustomServiceProviderFactory(_services, options)));
            })
            .UseStartup<MyStartup>()
            .Build();
Run Code Online (Sandbox Code Playgroud)

在 my 的构造函数中CustomServicesProvider,我还需要删除任何IHostedService服务,否则您会进入服务启动的无限循环。创建服务提供者时,我将所有内容从构造函数传递的服务集合添加到本地服务集合。

class CustomServiceProviderFactory : IServiceProviderFactory<IServiceCollection>
{
    private readonly IServiceCollection _baseServices;
    private readonly ServiceProviderOptions _options;

    public CustomServiceProviderFactory(IServiceCollection baseServices, ServiceProviderOptions options)
    {
        _baseServices = baseServices;
        _options = options;
        _baseServices.RemoveAll<IHostedService>();
    }
    
    public IServiceCollection CreateBuilder(IServiceCollection services)
    {
        return services;
    }

    public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder)
    {
        foreach (var service in _baseServices)
        {
            containerBuilder.Add(service);
        }

        return containerBuilder.BuildServiceProvider(_options);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,我可以Controller在添加后创建一个app.UseRouting()app.UseEndpoints(...)在我的启动类中。注入IMyService已成功解决,可以正常使用了。

app.ApplicationServices.GetRequiredService<IMyService>()您还可以通过添加方法来测试它Startup.Configure(),并查看是否返回了正确的服务。