3

My application is based on ASP.NET Core 2.1 and .NET Core 2.1 (downgraded from 2.2) generic host as Windows Service. So, IHostBuilder is launched first with other services and frameworks and then (if role permits) web service gets launched on top using IWebHostBuilder with all that WebHost.CreateDefaultBuilder(args).UseStartup<Startup>().StartAsync(). Secondary WebHost is another story; it is initialized and works, but I haven't checked yet if IoC replacement has the same trouble as generic host.

For now, generic host initialization:

new HostBuilder().ConfigureServices((hostContext, services) =>
{
    services.AddHostedService<LifetimeService>(); // Gets launched when host is up
    var container = ContainerBuilder.BuildBaseContainer(services, new WorkingPath());
    services.AddSingleton<IContainer>(container);
    services.AddStructureMap(); // Has no effect
});

IContainer initialization:

public static Container BuildBaseContainer(IServiceCollection services, IWorkingPath workingPath)
{
    var container = new Container();
    container.Configure(config =>
    {
        config.Scan(scan =>
        {
            workingPath.OwnLoadedAssemblies.Where(asm => !asm.IsDynamic).ForEach(scan.Assembly);
            scan.LookForRegistries();
            scan.AddAllTypesOf<IPlatformService>();
        });
        config.For<IContainer>().Use(() => container);                
        config.Populate(services);
    });
    container.AssertConfigurationIsValid();
    return container;
}

And the trouble is here, in the constructor of that registered hosted service (or anywhere else)

public LifetimeService(IEnumerable<IPlatformService> services,
                       IServiceProvider sp, IContainer c)
{
    var inCollection = services.Any();
    var inContainer = c.TryGetInstance<IPlatformService>() != default;
    var inProvider = sp.GetRequiredService<IPlatformService>() != default;
}

ps: IServiceProvider and IContainer are for demonstration purposes only, I only need 'services'

When LifetimeService is initialized during container.AssertConfigurationIsValid() I get
inCollection is true
inContainer is true
inProvider is true
IServiceProvider is StructureMapServiceProvider

Actual LifetimeService execution shows that
inCollection is false
inContainer is true
inProvider is false
IServiceProvider is ServiceProviderEngineScope

I don't plan to pass IServiceProvider or IContainer into constructors, but it seems that dependencies are resolved using IServiceProvider, not IContainer, and I get nulls. Silly thing like sp.GetRequiredService<IContainer>().TryGetInstance<IPlatformService>() does work.
There been some happy-path examples using WebHost and Startup classes where injection ought to be working properly. Doesn't seem relevant for generic host ...which might replace WebHost one day, but is little known and not widely used. Well, could be due to .NET Core version downgrade too, but quite unlikely. I've also tried replacing IServiceProvider and IServiceScopeFactory from IContainer during ConfigureServices() without luck. My idea is to replace or forward internal container to StructureMap. I might be misunderstanding how that should work...

Has anyone successfully tried to 'marry' generic host and external IoC?

1 Answer 1

4

I've solved the puzzle! Finally, according to a too much simplified example (https://github.com/aspnet/Hosting/blob/master/samples/GenericHostSample/ProgramFullControl.cs), I had to change HostBuilder initialization to

new HostBuilder()
.UseServiceProviderFactory(new StructureMapContainerFactory(workingPath))
.ConfigureServices((hostContext, services) =>
{                   
    services.AddHostedService<LifetimeService>();
});

and introduce provider factory itself

public class StructureMapContainerFactory : IServiceProviderFactory<IContainer>
{
    private readonly IWorkingPath workingPath;
    // pass any dependencies to your factory
    public StructureMapContainerFactory(IWorkingPath workingPath)
    {
        this.workingPath = workingPath;
    }

    public IContainer CreateBuilder(IServiceCollection services)
    {
        services.AddStructureMap();
        return ContainerBuilder.BuildBaseContainer(services, workingPath);
    }

    public IServiceProvider CreateServiceProvider(IContainer containerBuilder)
    {
        return containerBuilder.GetInstance<IServiceProvider>();
    }
}

Now internal container is substituted with StructureMap and resolved IServiceProvider in LifetimeService is of type StructureMapServiceProvider.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.