How do I inject dependencies into a custom WebHostService in a .Net Core project?

4.5k views Asked by At

I'm trying to create a service which will run as a Windows service as described here. My problem is that the example Web Host Service constructor only takes an IWebHost parameter. My service needs a constructor more like this:

public static class HostExtensions
{
    public static void RunAsMyService(this IWebHost host)
    {
        var webHostService =
            new MyService(host, loggerFactory, myClientFactory, schedulerProvider);
        ServiceBase.Run(webHostService);
    }
}

My Startup.cs file looks similar to this:

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

Obviously I'm using Simple Injector as a DI container. It's registered with the IServiceCollection as detailed in their documentation.

My question is how do I access the framework's container (the IServicesCollection) in the HostExtensions class so that I can inject the necessary dependencies into MyService? For MVC controllers that's all just handled under the covers, but I don't know of any documentation detailing how to access it where needed elsewhere.

1

There are 1 answers

2
Steven On BEST ANSWER

You just have to make a few minor tweeks to your code to get this working.

1. Make the Container a public static field in the Startup class:

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

2. Move the verification out of the Startup and into the Main:

Doing this allows extra registration to be added to the container, after the Startup class is done with it, but before the application is actuall started:

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. Register your MyService as singleton

Registering it explictly as singleton in the container allows Simple Injector to run diagnostics on it and prevents accidental captive dependencies that the MyService might accidentaly have. You should register it as singleton, because it will be kept alive for the duration of the application:

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

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

    Startup.Container.RegisterSingleton<MyService>();

    Startup.Container.Verify();

    ...
}

3. Register missing dependencies that MyService requires.

MyService depends on host, loggerFactory, myClientFactory and schedulerProvider, which are not all currently registered.

The host can be registered inside the Main method:

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

    Startup.Container.Verify();

    ...
}

While the loggerFactory can be registered inside the Startup class:

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

    ...
}

I assume that the myClientFactory and schedulerProvider dependencies are already registered in the container.

4. Replace the RunAsMyService extension method with simple resolve from the container

Since the MyService is successfully registered in the container with all its dependencies, we should now be able to resolve it from the container and pass it on to the ServiceBase.Run method:

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

    Startup.Container.Verify();

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

That should be all.

One last note, depending on the type of application you are building, you might not need such elaborate Startup class at all. You might move the configuration of the container closer to the Main method. Whether you should do this, depends on how much you actually need.

Here's an example of working without an Startup class:

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