Simple Injector integration with HostBuilder in .NET Core 2.x

1.4k views Asked by At

We are developing a windows service that runs .net core 2.x. Following this blog post by Steve Gordon running .netcore generic host applications as a service things seem to be working beautifully... as long as we use the IServiceCollection. I prefer SimpleInjector but I'm not sure how I can use it like I do in asp.net core. I there's a way to replace the built in DI as described here Default service container replacement and I know the SI team doesn't recommend the approach ASP.NET Core MVC Integration Guide so is there a better way in this use case?

Here is what I have so far but it's uncomfortable

--main program

internal class Program
{
    private static async Task Main(string[] args)
    {
        var isService = !(Debugger.IsAttached || args.Contains("--console"));

        var builder = new HostBuilder()
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<Runner>();
                //configure SimpleInjector here???
            });

        if (isService)
        {
            await builder.RunAsServiceAsync();
        }
        else
        {
            await builder.RunConsoleAsync();
        }
    }
}

Configuring the container here works more or less but the first class being created by the host (i.e. Runner in this case) gets created by the Host and injects any dependencies via the IServicesCollection. So my question is how do I have it inject from my SI container instead?

3

There are 3 answers

1
Steven On

The obvious answer here is... Don't have any dependencies injected into the Runner. Instead Runner is the class that represents your application entry point so I configure my container there and dispose of it when the Runner is stopped. The complete code for runner...

public class Runner : IHostedService, IDisposable
{
    private Container _container;
    public Runner()
    {
        _container = new Container();
        _container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        Bootstrapper.Bootstrap(_container);
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _container.Dispose();
        _container = null;
    }
}
0
Andrii Litvinov On

Generic host resolves hosted services from services collection, so the solution is to register hosted services in Simple Injector and then resolve them from Simple Injector to register in Services collection:

var container = new Container();
var host = new HostBuilder()
//...
    .ConfigureServices((context, services) =>
    {
        container.Collection.Append(typeof(IHostedService), typeof(Runner));
        services.AddSingleton(_ => container.GetAllInstances<IHostedService>());
    })
//...
    .Build();

container.Verify();
await host.RunAsync();
0
Thulani Chivandikwa On

I would hook onto the ConfigureContainer method of the HostBuilder and setup simpleinjectore there likke this:

                   HostBuilder()
                   .ConfigureContainer<ServiceCollection>((builder, services) =>
                   {
                       var container = new Container();

                       container.RegisterSingleton<IJobRepository, JobRepository>();
                       services.AddTransient<IHostedService, TimedService>();

                   })
                   .ConfigureServices((hostContext, services) =>
                   {
                       // Originally we would have done this
                       //services.AddHostedService<Service>();
                   })
                   .Build();

        using (host)
        {
            await host.StartAsync();
            await host.WaitForShutdownAsync();
        }

While you could use your IHostedService implementation indeed I think it may hide what is going on. I believe the infrastructure bootstrapping should be done in one place or orchestrated at least in one place. I consider the container to be infrastructure and would set it all up with the rest of the app via the HostBuilder methods.

An added advantage may also be that you do not entirely replace the ServiceCollection as it works well with doing other framework related things. An example of some stuff I would still do with the ServiceCollection:

                   HostBuilder()
                   .ConfigureServices((hostContext, services) =>
                   {
                       services.AddLogging();
                       services.AddOptions();
                   })

This is in line with what is stated in the simpleinjector docs about setting the container up with ASP.NET Core:

The practice with Simple Injector is to use Simple Injector to build up object graphs of your application components and let the built-in container build framework and third-party components,The practice with Simple Injector is to use Simple Injector to build up object graphs of your application components and let the built-in container build framework and third-party components

The same should apply with just .net core and the generic HostBuilder.