StructureMap.Microsoft.DependencyInjection icon indicating copy to clipboard operation
StructureMap.Microsoft.DependencyInjection copied to clipboard

Can I inititialize WebHostBuilder with and existing container?

Open hellfirehd opened this issue 7 years ago • 15 comments

The call to .UseStructureMap() expects nothing or a Registry. Either way it creates a brand new container.

I would prefer to use an existing container with cross cutting services that the entire application uses, not just WebHost.

This could be done if there was a .UseStructureMap(IContainer) overload. Is there any interest in this? Or is it a bad idea and if so, why?

hellfirehd avatar Mar 14 '17 00:03 hellfirehd

The whole reason there is a UseStructureMap method is for the ASP.NET Core host to automatically wire up the container. This means also calling Startup.ConfigureContainer with the specific TContainerBuilder, before the container is built. In this case, it would call it with an IContainer instance, which makes it a bit weird when you want to keep configuring the registry. I'll have to think a bit about this one.

khellang avatar Mar 15 '17 01:03 khellang

I understand that. In my app ASP.NET is only a small component. It's essentially only providing a Web UI/API to a long running daemon and database. The daemon relies heavily on IoC/DI and needs to share services with the ASP.NET bit.

In theory I could take the Deluge approach with a deluge.web and a deluge.daemon but I'd really rather have it all self contained in a single process that starts and stops together like NZBGet or Sonarr.

See where I'm going with this?

hellfirehd avatar Mar 15 '17 20:03 hellfirehd

@khellang @dougkwilson We suddenly need this feature at work for hybrid aspnet core/service bus apps. One of us will get a PR in for this one this week.

jeremydmiller avatar Apr 03 '17 13:04 jeremydmiller

There's already a PR at #24 (although that seems to have drifted pretty far out of scope by now). The question I have is; how valuable is this? Is this just a way to pass the container into Startup? The container is already built by the time it's passed into ConfigureContainer. What are you going to do with it?

khellang avatar Apr 03 '17 15:04 khellang

One way or another, we just need an easy recipe to build a new ASP.Net Core app by handing it an existing StructureMap Container. The immediate use case for us is an application that uses both a service bus framework and ASP.Net Core, and both need to add their own registrations to the StructureMap Container.

I think this is valuable. I was actually under the impression that we already had this before last week.

jeremydmiller avatar Apr 03 '17 15:04 jeremydmiller

Oh, I definitely think passing a pre-configure container is valuable, but I'm just not sure this is the way to go. If we bring this in as-is, it could be very confusing for the user; depending on which overload of UseStructureMap you call (the one taking a Registry or IContainer), the ASP.NET Core hosting layer will look for a different ConfigureContainer overload. This might not be obvious for the end user. Maybe we can rectify this with documentation?

khellang avatar Apr 03 '17 15:04 khellang

Instead of an overload of UseStructureMap, what if it's semantically different like UseExistingStructureMapContainer(IContainer)? I do agree on the potential user confusion. We had similar issues with that w/ fubumvc.

jeremydmiller avatar Apr 03 '17 16:04 jeremydmiller

Semantically different works for me. Would child or nested containers be appropriate?

hellfirehd avatar Apr 03 '17 22:04 hellfirehd

Child containers, maybe, nested no. If you were sharing the container w/ some other piece of infrastructure but needed to override some things, I guess a Child container would work.

jeremydmiller avatar Apr 03 '17 22:04 jeremydmiller

So is there currently a way to use an existing container?

joelweiss avatar Oct 16 '17 16:10 joelweiss

You can pass initialized container to Startup class directly.

  public class Startup : IStartup {
        private IContainer _container;
        public Startup(IConfiguration configuration, IContainer container)
        {
            Configuration = configuration;
            _container = container;
       }

        public IConfiguration Configuration { get; }

        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            // Register additional stuff
            // .....

            // populate the container using the service collection.
            _container.Populate(services);

            return _container.GetInstance<IServiceProvider>();
        }
        public void Configure(IApplicationBuilder app)
        {
             //do additional work 
        }
    }

After that you should instantiate Startup and build host

var container = new Container();
// configure container
// .......

var configurationBuilder = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json");

var configuration = configurationBuilder.Build();

var startup = new Startup(configuration, container);

var builder = WebHost.CreateDefaultBuilder()
    .UseSetting(WebHostDefaults.ApplicationKey, typeof(Startup).Assembly.GetName().Name)
    .ConfigureServices(services => services.AddSingleton<IStartup>(startup))
    .UseKestrel(options =>
    {
        options.Listen(IPAddress.Loopback, 1113);
    });

var host = builder.Build();

You can read this article for some details.

But be careful with child container, I don't know why, but some dependencies (e.g. registered as singleton or open generic) cannot be resolved, when using child container.

This code will fail

var container = new Container();

var configurationBuilder = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json");

var configuration = configurationBuilder.Build();

var startup = new Startup(configuration, container.CreateChildContainer());

var builder = WebHost.CreateDefaultBuilder()
    .UseSetting(WebHostDefaults.ApplicationKey, typeof(Startup).Assembly.GetName().Name)
    .ConfigureServices(services => services.AddSingleton<IStartup>(startup))
    .UseKestrel(options =>
    {
        options.Listen(IPAddress.Loopback, 1113);
    });

var host = builder.Build();

Above code will fail, when try to resolve IServer instance within WebHost class, with exception StructureMap.StructureMapConfigurationException : No default Instance is registered and cannot be automatically determined for type 'IOptions<KestrelServerOptions>'

zordark avatar Oct 31 '17 09:10 zordark

I'm also interested in a way to use an existing container. Our solution has console apps, Windows services, web apps that all use the same logic to initialize the container. Is there a good example to achieve this with ASP.NET Core 2.1 ?

RubenDelange avatar Jun 26 '18 14:06 RubenDelange

@zordark

Above code will fail, when try to resolve IServer instance within WebHost class, with exception StructureMap.StructureMapConfigurationException : No default Instance is registered and cannot be automatically determined for type 'IOptions<KestrelServerOptions>'

I am also having a problem with IOptions<T>. Perhaps this is related to #43

I have submitted a PR with a failing test case to demonstrate the issue, but I haven't been able to fix it.

dazinator avatar Aug 26 '18 13:08 dazinator

I'm also having problems with this in ASP.NET Core 3 for months now. I'm using SassKit library and it requires an existing container.

whizkidwwe1217 avatar Jul 27 '19 02:07 whizkidwwe1217

@whizkidwwe1217 perhaps try dotnettency instead of saaskit? I created it to solve issues I was having with saaskit and it works with asp.net core 3.0: https://github.com/dazinator/Dotnettency

I tend to use the autofac package rather than the structuremap though, as seen here: https://github.com/dazinator/Dotnettency/blob/develop/src/Sample.AspNetCore30.RazorPages/Sample.AspNetCore30.RazorPages.csproj so if you do use the structuremap package your mileage may vary

dazinator avatar Jul 27 '19 10:07 dazinator