2

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?

Steven
  • 860
  • 6
  • 24
  • I'm not sure I follow your question and what the problem is you are encountering. While integrating Simple Injector with ASP.NET Core, you would _never_ replace the built-in container, and so shouldn't you with something that runs as a service. So you'd follow an identical approach where you would plug in into the framework by replacing one of its main interception points, such as `IControllerActivator`. So what is preventing you from taking that approach? – Steven Sep 19 '18 at 19:17
  • 1
    @Steven yeah I agree the question was a bit vague and I was hoping the blog post from Steven Gordon would make it clear. I have edited the question and will post the now obvious answer in a minute – Steven Sep 20 '18 at 12:23
  • @Steven also, the host builder here doesn't have an IControllerActiviator because I'm not using ASP.NET core. :) – Steven Sep 20 '18 at 12:29
  • But are you doing than? A request comes in, and what happens at that point? – Steven Sep 20 '18 at 12:57
  • @Steven ah, the "requests" are actually rebus messages. So my bootstrapper configures the service bus (rebus) which does the polling and uses the SI adapter to build up the message handler dependencies as required. – Steven Sep 20 '18 at 17:56
  • Ah okay. So the question is: how to integrate Simple Injector with Rebus while working with .NET Core? – Steven Sep 20 '18 at 19:49
  • well I have rebus integration down, it was more about this new Host type which isn't the same as the WebHost type in ASP.NET Core. But as you can see from my own answer, I don't need to plug into that at all because the `Runner` class is my service class with a lifetime controlled by the Host and inside that I use SI to handle my rebus dependencies. – Steven Sep 20 '18 at 21:13

3 Answers3

1

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;
    }
}
Steven
  • 860
  • 6
  • 24
0

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.

Thulani Chivandikwa
  • 3,402
  • 30
  • 33
0

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();
Andrii Litvinov
  • 12,402
  • 3
  • 52
  • 59