如何在.Net Core项目中将依赖项注入到自定义的WebHostService中?

5
我正在尝试创建一个服务,该服务将运行为Windows服务,如此处所述。我的问题是,示例Web主机服务构造函数仅接受IWebHost参数。我的服务需要一个更像这样的构造函数:
public static class HostExtensions
{
    public static void RunAsMyService(this IWebHost host)
    {
        var webHostService =
            new MyService(host, loggerFactory, myClientFactory, schedulerProvider);
        ServiceBase.Run(webHostService);
    }
}

我的Startup.cs文件看起来类似于这样:

public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder()
        .AddJsonFile("appsettings.json")
        .AddEnvironmentVariables()
        .AddInMemoryCollection();

    this.Configuration = builder.Build();
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddOptions();
    this.container.RegisterSingleton<IConfiguration>(this.Configuration);
    services.AddSingleton<IControllerActivator>(
        new SimpleInjectorControllerActivator(container));
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, 
    ILoggerFactory loggerFactory)
{
    app.UseSimpleInjectorAspNetRequestScoping(this.container);

    this.container.Options.DefaultScopedLifestyle = new AspNetRequestLifestyle();

    this.InitializeContainer(app, loggerFactory);
    this.container.Verify();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
}

private void InitializeContainer(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
    container.Register(() => loggerFactory, Lifestyle.Singleton);
    container.Register<IMyClientFactory>(() => new MyClientFactory());
    container.Register<ISchedulerProvider>(() => new SchedulerProvider());
}

显然我正在使用Simple Injector作为DI容器。它已在IServiceCollection中注册,如他们的文档所述。
我的问题是如何在HostExtensions类中访问框架的容器(即IServicesCollection),以便我可以将必要的依赖项注入到MyService中?对于MVC控制器,这一切都是在后台处理的,但我不知道有哪些文档详细说明了在其他地方需要访问它的方法。

我很好奇,为什么在 .Net Core 中自带的 DI 容器(IServiceCollection)已经很好用了,还要使用另一个 DI 容器呢?这似乎是增加了一个完全不必要的层。有什么优势吗? - R. Richards
1
@R.Richards 内置的容器不太适合构建围绕SOLID原则构建的更大型应用程序,如这里所解释的那样。 - Steven
1个回答

3

您只需要对代码进行一些小的调整,就可以使其正常工作。

1. 在Startup类中将Container设置为public static字段:

public class Startup
{
    public static readonly Container container = new Container();

2. 将验证移出Startup并移到Main中:

这样做可以允许在Startup类完成后,但在应用程序实际启动前向容器添加额外的注册信息:

public static void Main(string[] args)
{
    ...
    var host = new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(directoryPath)
        .UseStartup<Startup>()
        .Build();

    // Don't forget to remove the Verify() call from within the Startup.
    Startup.Container.Verify();

    ...
}

3. 将您的 MyService 注册为单例

在容器中显式地将其注册为单例,允许 Simple Injector 对其运行诊断,并防止 MyService 可能会意外拥有的囚徒依赖关系。您应该将其注册为单例,因为它将保持应用程序的持续时间而活着:

public static void Main(string[] args)
{
    ...

    var host = new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(directoryPath)
        .UseStartup<Startup>()
        .Build();

    Startup.Container.RegisterSingleton<MyService>();

    Startup.Container.Verify();

    ...
}

3. 注册 MyService 需要的缺失依赖项。

MyService 依赖于主机、loggerFactory、myClientFactory 和 schedulerProvider,这些依赖项目前并未全部注册。

host 可以在 Main 方法内进行注册:

public static void Main(string[] args)
{
    ...
    Startup.Container.RegisterSingleton<MyService>();
    Startup.Container.RegisterSingleton<IWebHost>(host);

    Startup.Container.Verify();

    ...
}

虽然loggerFactory可以在Startup类中注册:

public void Configure(IApplicationBuilder app, IHostingEnvironment env,
    ILoggerFactory loggerFactory)
{
    Container.RegisterSingleton(loggerFactory);

    ...
}

我假设myClientFactoryschedulerProvider的依赖关系已经在容器中注册。

4. 使用容器简单解析代替RunAsMyService扩展方法

由于MyService已经成功地在容器中注册了所有依赖项,我们现在应该能够从容器中解析它,并将其传递给ServiceBase.Run方法:

public static void Main(string[] args)
{
    ...

    Startup.Container.Verify();

    ServiceBase.Run(Startup.Container.GetInstance<MyService>());
}

这应该就是全部了。

最后注意一点,根据您构建的应用程序类型,您可能根本不需要如此复杂的Startup类。 您可以将容器的配置移到更靠近Main方法的位置。是否应该这样做取决于您实际需要多少。

下面是一个不使用Startup类的示例:

public static void Main(string[] args)
{
    var container = new Container();
    container.Options.DefaultScopedLifestyle = new AspNetRequestLifestyle();

    IWebHost host = new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseIISIntegration()
        .ConfigureServices(services =>
        {
            // Configure framework components
            services.AddOptions();
        })
        .Configure(app =>
        {
            app.UseSimpleInjectorAspNetRequestScoping(container);

            // Apply cross-wirings:
            container.RegisterSingleton(
                app.ApplicationServices.GetRequiredService<ILoggerFactory>());
        })
        .UseStartup<Startup>()
        .Build();

    container.RegisterSingleton<MyService>();
    container.RegisterSingleton(host);

    container.Verify();

    ServiceBase.Run(Startup.Container.GetInstance<MyService>());
}

我遇到了同样的问题,您能否详细说明一下容器是什么? - User7291
2
我相信你没有遇到同样的问题,因为如果是这种情况,你肯定知道容器是什么;这个问题是关于Simple Injector的。 - Steven

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