一个DI容器可以管理多个主机吗?

4
在C#.NET Core中,您可以使用以下代码创建通用主机:
IHostBuilder builder = new HostBuilder()
    .ConfigureServices((context, collection) => {
        collection.AddSingleton<IMyClass, MyClass>();
        collection.AddHostedService<MyService>();
    });
await builder.RunConsoleAsync();

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

现在,假设我想在 MyService 内部创建一个新主机。这很容易(在本例中是 Web 主机):

IWebHost webHost = WebHost.CreateDefaultBuilder()
    .UseStartup<MyStartup>()
    .Build();
    .RunAsync();

这个网络主机将拥有自己的依赖注入容器,因此它将无法访问我已经添加到通用主机容器中的所有依赖项:也就是说,它将无法将IMyClass注入MyStartup。
我还尝试使用以下代码添加自定义的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;
    }
}

在我的HostBuilder中通过.UseServiceProviderFactory(new CustomServiceProviderFactory(_serviceProvider))添加它,但由于这是在创建之前实例化HostedService,导致DI异常,报告找不到所需的对象。
然而,鉴于WebHost.CreateDefaultBuilder()现在是创建Web主机的首选方法(在.NET Core 3.0中),并且IWebHostBuilder没有设置自定义IServiceProviderFactory的选项,因此这似乎是一个死胡同。
如何让Web主机使用与初始泛型主机相同的DI容器?

目标是真正共享容器和/或其中的服务吗?您不能将容器配置逻辑放在一个中央位置,然后通过调用此逻辑来类似地配置容器吗?我意识到如果您需要共享单例或某些有状态的内容,则可能会出现错误。也许您可以补充您的问题并澄清一下。您正在尝试做的听起来很有趣,但可能超出了Core的能力范围。只是一个想法。 - Kit
@Kit 我确实需要共享单例和有状态的依赖项 :) - GTHvidsten
一种可能性是将两者的默认容器切换为类似于Autofac的东西,然后在这两个容器中添加一个共享子容器。因此,您仍然拥有不同的容器,但两者都有一个共享的子容器。 - Kit
我考虑过不同的容器,但我真的想要一个本地解决方案。 - GTHvidsten
@GTHvidsten,你能想到解决方案了吗? - Angelo
我也对这个问题很感兴趣,有没有内置的解决方案? - Not Important
1个回答

1

我尝试做同样的事情,这是我想到的解决方案。虽然没有完全测试,但似乎可以使用。

首先,在我的基本/第一个HostBuilder中,将服务集合添加为服务,以便稍后可以通过DI解析IServiceCollection

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

IHostedService.StartAsync()中,我创建了WebHost。我从UseDefaultServiceProvider()内部的功能复制了services.Replace的使用方法:
IWebHost host = WebHost
            .CreateDefaultBuilder()
            .ConfigureServices(services =>
            {
                var options = new ServiceProviderOptions();               
                services.Replace(ServiceDescriptor.Singleton<IServiceProviderFactory<IServiceCollection>>(new CustomServiceProviderFactory(_services, options)));
            })
            .UseStartup<MyStartup>()
            .Build();

在我的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);
    }
}

在我的启动类中添加app.UseRouting()app.UseEndpoints(...)之后,我成功创建了一个Controller。成功解决了注入IMyService的问题,我可以像平常一样使用它。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接