Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discussion: ContainerBuilder.Update Marked Obsolete #811

Closed
tillig opened this issue Nov 15, 2016 · 98 comments
Closed

Discussion: ContainerBuilder.Update Marked Obsolete #811

tillig opened this issue Nov 15, 2016 · 98 comments
Labels

Comments

@tillig
Copy link
Member

tillig commented Nov 15, 2016

In 8a89e94 the ContainerBuilder.Update methods have been marked obsolete.

The plan is to leave them obsolete for a year or more, weaning developers off the use of Update, and in a future major release remove the methods altogether.

This issue is to learn from developers why they believe they need Update and see if there are better ways to handle the situations. If it turns out there is a totally unavoidable situation where Update is literally the only way to handle it, we need to determine how best to fix Update.

If You Want to Keep ContainerBuilder.Update...

If you believe you need ContainerBuilder.Update please provide the following information with your comment - "me too" or "I use it" isn't enough.

  • Which overload do you use? Update(IContainer), Update(IContainer, ContainerBuildOptions), or Update(IComponentRegistry)?
  • What is the scenario (use case) in which you are unable to pass the ContainerBuilder prior to container building?
  • When you use Update, have you already resolved something from the container before calling Update?

We actually need real information to understand the use cases and see if there are different ways your code could be doing things to work around needing Update. If it turns out we're missing a feature, hopefully we can figure out what that is and get you what you need while at the same time removing the need to update the container post-facto.

Why ContainerBuilder.Update Was Marked Obsolete

Here are some of the reasons why the use of ContainerBuilder.Update is generally bad practice and why we're looking at this.

Container Contents Become Inconsistent

Once you resolve something out of a built container, a lot of things get put in motion.

  • If it's a singleton, its dependency chain gets resolved and the singleton gets cached.
  • Anything marked AutoActivate or IStartable gets resolved.
  • Disposable items may be cached and/or tracked.

If you change the contents of the container, there's a chance that the change will actually affect these things, rendering cached or tracked items as inconsistent with the current contents of the container.

In unit test form (with a little pseudocode)...

[Fact]
public void InconsistentContainer()
{
  var builder = new ContainerBuilder();

  // In this example, the "HandlerManager" takes a list of message handlers
  // and does some work with them.
  builder.RegisterType<HandlerManager>()
         .As<IHandlerManager>()
         .SingleInstance();

  // Register a couple of handlers that the manager will use.
  builder.RegisterType<FirstHandler>().As<IHandler>();
  builder.RegisterType<SecondHandler>().As<IHandler>();

  using (var container = builder.Build())
  {
    // The manager is resolved, which resolves all of the currently registered
    // handlers, and is cached. This manager instance will have two handlers
    // in it.
    var manager = container.Resolve<IHandlerManager>();

    // Update the container with a new handler.
    var updater = new ContainerBuilder();
    updater.RegisterType<ThirdHandler>().As<IHandler>();
    updater.Update(container);

    // The manager still only has two handlers... which is inconsistent with
    // the set of handlers actually registered in the container.
    manager = container.Resolve<IHandlerManager>();
  }
}

Update Isn't Consistent With Build

When you Build a container, a couple of things happen:

  • A set of default registration sources is added to the container. This is how things like IEnumerable<T> and Func<T> are resolved. These should only be added to the container one time.
  • Startable components (AutoActivate and IStartable) are automatically resolved.

When you Update a container, these things don't happen. We intentionally don't want the base registration sources duplicated; and unless you manually specify it we don't run startable components that may have been added during an update. Even if you specify it, your existing startable components aren't re-run; only the newly added ones would be run.

Why not fix it? There's not a clear "right way" to do that due to the container contents being inconsistent (see above). Most startable components are singletons where the point of starting them is to initialize a cache or execute some other startup logic proactively instead of lazily. If we don't re-run startables, maybe they don't pick up the things that they need. If we do re-run startables, maybe that invalidates even more things... or maybe it doesn't have any effect (in the case of singletons).

Child lifetime scopes spawned from the container get a sort of "copy" of the set of registrations in the base container. Updating the container after a child lifetime scope is spawned doesn't automatically propagate the new registrations into the child scope (Issue #608).

Basically, Update really isn't the same as Build and using it may not be doing 100% of the things you think it's doing.

Diagnostics and Optimizations Difficult to Implement

We see a lot of StackOverflow questions, issues, tweets, etc. about some of the challenges folks have around diagnosing missing dependencies. We'd love to be able to provide some better diagnostics, but one of the challenges in that is with Update: If folks assume they can change the container contents later, we conversely can't assume we can do any sort of proactive analysis or diagnostics when a container is built.

Further, we could potentially implement optimizations that, for example, proactively cache component activation data based on the registered set of components... but doing that and making sure it's all flushed/regenerated on each update doesn't always turn out to be the easiest thing to do.

Why Not Just "Fix" Update?

The question that logically follows is... why not just "fix Update so it behaves correctly?"

Easier Said Than Done

If you think about what would actually have to happen to make Update work "correctly" it includes...

  • Flush all caches of singletons, disposing of tracked disposables, so singletons can be regenerated with the updated container contents.
  • Invalidate all child lifetime scopes currently spawned because the root container has changed. (Note that we currently don't track child lifetime scopes, so that would also be something we'd need to change.)
  • Re-run all startable components so they are created with the new container contents.

...and so on. Basically, rebuild the whole container. This can really mess with your app if have something that's holding onto a resolved item that gets disposed or becomes invalid.

Locking a Container Isn't Unprecedented

Looking at other containers out there, locking a container after it's built and ready to resolve isn't uncommon. Simple Injector, LightInject, and the Microsoft.Extensions.DependencyInjection containers all disallow updating the container post-facto.

For those that do - StructureMap, Ninject, and Windsor to name a few - it appears they actually do all the work mentioned in "easier said than done" - flushing caches, rebuilding the whole container. And, as mentioned, this can cause inconsistent behavior in the application if it isn't managed very, very carefully.

Possible Workarounds for Update

Instead of using ContainerBuilder.Update, you may be able to...

Pass Around ContainerBuilder Instead of IContainer

Instead of passing the container around and conditionally updating it, change your logic to pass around the ContainerBuilder and conditionally register things correctly the first time. With the new ContainerBuilder.Properties dictionary available, you can add some context and perform business logic during the initial building of the container if you need to rather than wait until afterwards.

Add Registrations to Child Scopes

Occasionally what you need is something available during a child lifetime scope for a specific task, unit of work, or request. You can add registrations to just that child scope using a lambda:

using (var scope = container.BeginLifetimeScope(b =>
  {
    b.RegisterType<NewRegistration>();
  })
{
  // The new registration is available in this scope.
}

You may even want to cache that child lifetime scope and reuse it - like a smaller sub-container with a special purpose. That's how the multitenant integration works - cached lifetime scopes per tenant.

Use Lambdas

If you're trying to change something that's registered based on an environment parameter or some other runtime value, register using a lambda rather than reflection:

builder.Register(ctx =>
{
  if (Environment.GetEnvironmentVariable("env") == "dev")
  {
    return new DevelopmentService();
  }
  else
  {
    return new ProductionService();
  }
}).As<IMyService>();

Use Configuration

Just like with web.config transforms, you may choose to switch deployed Autofac configuration files based on an environment. For example, you may have a development configuration and a production configuration.

Use Modules

If you have a lot of registrations that need to change based on runtime, you can encapsulate that in a module.

public class MyModule : Module
{
  private readonly bool _isProduction;

  public MyModule(bool isProduction)
  {
    this._isProduction = isProduction;
  }

  protected override void Load(ContainerBuilder builder)
  {
    if (this._isProduction)
    {
      builder.RegisterType<FirstProductionService>().As<IMyService>();
      builder.RegisterType<SecondProductionService>().As<IOtherService>();
    }
    else
    {
      builder.RegisterType<FirstDevelopmentService>().As<IMyService>();
      builder.RegisterType<SecondDevelopmentService>().As<IOtherService>();
    }
  }
}

Use Conditional Registrations

Autofac 4.4.0 introduced OnlyIf() and IfNotRegistered extensions. These allow you to execute a specific registration only if some other condition is true. Here's the documentation. Quick example:

// Only ServiceA will be registered.
// Note the IfNotRegistered takes the SERVICE TYPE to
// check for (the As<T>), NOT the COMPONENT TYPE
// (the RegisterType<T>).
builder.RegisterType<ServiceA>()
       .As<IService>();
builder.RegisterType<ServiceB>()
       .As<IService>()
       .IfNotRegistered(typeof(IService));

Handling Application Startup / Bootstrap Items

A common scenario for wanting to update the container is when an app tries to use a container to register plugins or perform app startup actions that generate additional registrations. Ideas for handling that include:

Consider Two Containers

If you are using DI during app startup and then also using it during the execution of the app, it may be that you need two containers: one for each stage in the app lifecycle.

The first is a container that has services used to index plugins (the "assembly scanning" mechanism, logging, that sort of thing); the second is a container into which runtime requirements are registered like the set of plugin assemblies, required common dependencies, and so on.

Don't Over-DI Bootstrap Items

It's good to use DI, but you can easily get into a chicken/egg situation where you try to resolve bootstrap items (like your application configuration system, logging that will run during application startup, and so on) out of the container... that you're trying to set up during app startup.

Don't do that.

If you look at many of the newer ASP.NET Core examples, you'll see a good pattern where app configuration, base logging, and other "bootstrap" elements are actually just directly instantiated or built. Those instances/factories can then later be registered with Autofac for use during runtime, but the initial construction proper isn't done out of Autofac.

Share Instances Across Containers

If you go with the two-container startup, you can always register the same instance of a thing (e.g., application configuration, logging factory, etc.) into two different containers. At that point it's effectively a singleton.

(You can also use Autofac modules to share registrations if you have bunches of them that need to be duplicated, though you'll get different instances of things so be aware.)

Lambdas, Lambdas, Lambdas

Many, many container updates could be worked around using a lambda registration. "I need to register XYZ based on the result of resolving ABC!" - do that with a lambda registration.

Nancy Framework Users

If you use Nancy, it internally uses Update(). There is already an issue filed for Nancy to be updated - you can follow that issue or chime in over there if you're interested in how that is progressing.

Prism (WPF) Framework Users

Prism only supports modules in mutable containers. This is an architectural choice of the Prism project owners. An issue was filed here to alert Prism of the changes around Update() and as part of a major IoC integration refactor the decision was made to only support modules for mutable containers. While it may be possible to enable modules for Autofac via one of the above strategies or something like registration sources, Prism is expecting the community to submit and support that code. Head over there if you'd like to follow up with them; there is no current plan to create an Autofac-project-supported Prism integration library.


Short Term Fix

If your code uses Update and you want to keep using it for the time being, you can disable the warning just around that call.

#pragma warning disable 612, 618
builder.Update(container);
#pragma warning restore 612, 618
@tillig
Copy link
Member Author

tillig commented Nov 15, 2016

I believe you could get around the if(!registered) by using PreserveExistingDefaults:

builder.RegisterType<SomeService>().As<IService>();
// then later
builder.RegisterType<SomeOtherService>().As<IService>().PreserveExistingDefaults();

What that does is add the registration but make sure that previous registrations are used first, so if you have a singleton, you'd still get a previous registration first. The only time that may affect you is if you're resolving IEnumerable<T> - you'd get both services, not just one.

A new way you may be able to work around it is to use the Microsoft.Extensions.DependencyInjection abstraction - less full featured but allows for that sort of pre-build manipulation.

But otherwise, what I'm hearing is that you may be able to get away from Update if you had an IfNotExists registration extension as outlined in #469. Does that sound about right?

Note that part of what we're trying to do is not only provide a good container but push people towards better practices and help them avoid pitfalls. Bringing over practices from one container to another isn't something we're really interested in addressing since each container has its own quirks. On the other hand, it is interesting to provide a good feature set; get people to use good IoC practices that avoid container inconsistency; and be able to bring the container forward by adding new features.

@twirpx
Copy link

twirpx commented Nov 24, 2016

There is scenario in which I have to use Update(IContainer):

  1. At application start container is building from configuration classes, data access classes, services, logging staff etc.
  2. Then instance of www-host service class resolved though container.
  3. This class creates dotnetcore IWebHostBuilder, IWebHost and starts listening through Kestrel
  4. At this point I have to pass startup-class that contains something like this:
public IServiceProvider ConfigureServices(IServiceCollection services) {
    IContainer container = Program.Container;

    // configuring MVC services here

    // I should create new container, populate MVC services into it
    // and then update main container to be able to use it instead of
    // built-in aspnetcore DI container
    ContainerBuilder builder = new ContainerBuilder();
    builder.Populate(services);
    builder.Update(container);

    return container.Resolve<IServiceProvider>();
}

How can I rewrite this scenario?

@RaphyFischer
Copy link

RaphyFischer commented Nov 24, 2016

Hey @tillig ,

I am quite new to AutoFac and want to replace Unity in my application with AutoFac. When I tried this I ran into some issues.Most of them were quite easy to solve. My main problem on the other hand "seemed" to be easy to solved, by using the ContainerBuilder.Update() function until I ran into this posting #811 here on GitHub. But first let's provide some more information about what I use exactly and why I use it.

Which overload do you use? Update(IContainer), Update(IContainer, ContainerBuildOptions), or Update(IComponentRegistry)?

The overload I use is Update(IContainer).

When you use Update, have you already resolved something from the container before calling Update?

Yes I do resolve some services, before all registrations are registered. Why this is the case I will explain within the use case below.

What is the scenario (use case) in which you are unable to pass the ContainerBuilder prior to container building?

The application I want to integrate AutoFac into is a ASP.NET WebAPI 2 based Web Service with OWIN integration. What I need to do at the startup of the application is registering services (which hold some configuration stuff so it is easily accessable with DI) and do registrations based on the config informations which are held by the mentioned services.

To make this more clear here is some pseudo sample code:

public void Startup()
{
        var builder = new ContainerBuilder();
        builder.RegisterType<ServiceWithConfigInformation>();

       //... do some more registrations and stuff

      var container = builder.Build();
      configService = container.Resolve<ServiceWithConfiguration>();
     
      if(configService.SomeThingIsSet)
     {
            builder = new ContainerBuilder();
            builder.RegisterType<SomeService>();
            builder.Update(container);
     }
      
     //... going on with the startup of the application.
}

I know this code is not correct in some ways, but I just wanted to display my situation. Also the resolving sometimes does happen in another class so it would not be possible for me to hold an instance locally and use this. This is not the only case I need this and not the only service I need that for.

The application also uses extensions (MEF and Microsoft Addin Framework) to extend it's funcionality. So there is an use case where additional registrations have to be made when the application is already running. The application also supports updating and installing these extensions on runtime, and changing configurations on runtime. So in this case I may have to change some registrations, remove or update them.
I totally understand, that updating an existing container is not the best practice but in my case I didn't see any possiblity to come around this.

The next thing I evaluated were your suggestions using modules or lambdas to extend a container/scope with registrations.

Use Modules

Use Lambdas

In principal I would be ok with these solutions because I could solve most of my problems with it. But I wondered how these functions handle additional registrations for a container/scope. So I went through the sources and discovered, that these functions also use the ContainerBuilder.Update() function. Now I am confused... will the modules and lambdas work in future releases or will this be kicked out as well, or do you have any other solution to this? I saw, that these use the overload : ContainerBuilder.Update(IComponentRegistry) will this stay supported?

So in conclusion: Is there any way to implement my use case with AutoFac in an "correct" way and if not, will modules or lambdas still be supported in future releases?

Thank you!

@alexmg
Copy link
Member

alexmg commented Nov 24, 2016

@twirpx Is your issue with WebHostBuilder the one described below?

aspnet/Hosting#829

If so an IServiceProviderFactory implementation might help. That interface is now part of Microsoft.Extensions.DependencyInjection.Abstractions and is something we could provide an implementation of in Autofac.Extensions.DependencyInjection.

https://github.com/aspnet/DependencyInjection/blob/dev/src/Microsoft.Extensions.DependencyInjection.Abstractions/IServiceProviderFactory.cs

@twirpx
Copy link

twirpx commented Nov 24, 2016

@alexmg It seems that it is not a solution. It is just another way to pass IServiceProvider to dotnet dependency injection mechanism.

The problem is that the container building happens before WebHostBuilder initialization.
And this two steps cannot be swapped in order because code creating WebHostBuilder depends on externals that got through IoC.

@alexmg
Copy link
Member

alexmg commented Nov 24, 2016

Hi @Raphy1302.

The first thing that comes to mind looking at the example code is what happens when something asks for the service that wasn't actually registered based on configuration? The same conditional logic would end up having to be used at the point of consuming the service, and most likely without that service taking a direct dependency, which implies the need for some kind of service location anti-pattern.

The Null Object pattern with a lambda based registration that checks the configuration state could remove the need to update the container in a case such as this.

var builder = new ContainerBuilder();
builder.RegisterType<ServiceWithConfiguration>();

builder.Register<ISomeService>(c =>
{
	var config = c.Resolve<ServiceWithConfiguration>();
	if (config.SomeThingIsSet)
		return new SomeService();
	else
		return new NullSomeService();
});

Other services could still take a dependency on ISomeService without having to first check if the configuration flag was set or not. It would instead perform the appropriate NOOP behaviour.

Would something like that get you out of trouble if the Update method wasn't available?

@RaphyFischer
Copy link

Hey @alexmg

Thanks for your quick response!
This will partially solve my problems.
What you pointed out is correct and not the best way, but in this application this case will be not a big problem, because it is controlled by the developer who creates a extension. Also this will be changed and the extension (which consumes the service on runtime) has to enable the registration in a config file.
My bigger problem here is, that the application will load such extensions on runtime. Therefor the services for this extension will be loaded when the extension is installed. Also the application should be able to change configurations on runtime.

What is about my last point I mentioned about the modules and lambda expressions. I still wonder how this is gonna work when the update function is not available anymore?

Thanks!

@huan086
Copy link

huan086 commented Nov 30, 2016

My use case is to inject the IContainer itself into the constructor, so that I could use it in using (ILifetimeScope scope = this.dependencyContainer.BeginLifetimeScope())

See http://stackoverflow.com/a/22260100/263003

var builder = new ContainerBuilder();

// register stuff here

var container = builder.Build();

// register the container
var builder2 = new ContainerBuilder();
builder2.RegisterInstance<IContainer>(container);
builder2.Update(container);
public class WindowService : IWindowService
{
    private readonly IContainer container;

    public WindowService(IContainer container)
    {
        this.container = container;
    }
}

@osoykan
Copy link

osoykan commented Nov 30, 2016

hi @huan086,

i've solved your problem using a different way which is also available in nuget package and tested.
Project: Autofac.Extras.IocManager
I hope this would be helpful.

@tillig
Copy link
Member Author

tillig commented Nov 30, 2016

@huan086 If you need the container, it implies you're doing some service location. I'd recommend using the solution from @osoykan or another service locator like CommonServiceLocator rather than forcing the container to be able to resolve itself.

@huan086
Copy link

huan086 commented Dec 1, 2016

@osoykan @tillig Don't think I'm using service locator. All I really need is to BeginLifetimeScope, which happens to be a method on IContainer.

I realize that I don't need the Update method after all. I shouldn't be injecting IContainer. I should inject ILifetimeScope instead. Autofac already register that automatically, and I can use ILifetimeScope.BeginLifetimeScope.

See http://stackoverflow.com/questions/4609360/resolve-icontainer

@arieradle
Copy link

arieradle commented Dec 1, 2016

Which overload do you use? Update(IContainer), Update(IContainer, ContainerBuildOptions), or
Update(IComponentRegistry)?

The overload I use is Update(IContainer).

When you use Update, have you already resolved something from the container before calling Update?

Yes, consider the following example:

builder.Register ... 
...
var container = builder.Build();

var extenders = container.Resolve<IEnumerable<ISomeExtender>>()
extenders.ForEach(e => e.ConfigureODataStuffHereForEachModule(HttpConfiguration)); 
// has to happen here, while I'm still configuring WebApi and OData

var updater = new Autofac.ContainerBuilder();
updater.RegisterWebApiFilterProvider(HttpConfiguration);

updater.Update(container);
...

The removal of the "Update" method will force me to manage two different containers: one for extensibility and other for general logic - one big mess.

@tillig
Copy link
Member Author

tillig commented Dec 1, 2016

@arieradle What stops you from registering the filter provider when building the container the first time? No requests would be running through the system at the time so it shouldn't matter.

@arieradle
Copy link

@tillig RegisterWebApiFilterProvider requires HttpConfiguration object to be ready I presume.

Will RegisterWebApiFilterProvider work if I still need to register some OData points and attribute routing for WebApi after it runs?

@tillig
Copy link
Member Author

tillig commented Dec 1, 2016

@arieradle It needs the config object so it can add itself as the filter provider, but it won't resolve or really do anything until a request comes in. Give it a shot. I think you'll be fine, and it would mean no call to Update.

@arieradle
Copy link

@tillig Could be, I'll try.

Have another issue:

var signalrResolver = new Autofac.Integration.SignalR.AutofacDependencyResolver(container);
GlobalHost.DependencyResolver = signalrResolver;

var updater = new ContainerBuilder();

updater.RegisterInstance(signalrResolver.Resolve<Microsoft.AspNet.SignalR.Infrastructure.IConnectionManager>());

updater.RegisterType<SignalRAssemblyLocator>().As<Microsoft.AspNet.SignalR.Hubs.IAssemblyLocator>().SingleInstance();
            
updater.Update(container);

@tillig
Copy link
Member Author

tillig commented Dec 1, 2016

@arieradle Well, there are two things there. Let's tackle each in turn.

The second line is the easiest:

updater.RegisterType<SignalRAssemblyLocator>().As<Microsoft.AspNet.SignalR.Hubs.IAssemblyLocator>().SingleInstance();

There's no reason you couldn't register that earlier. It's not being resolved or activated, it's just a registration.

The first line is a tiny bit trickier and I'm not sure why you need it. The way I'm reading it...

  • The container is built.
  • An object is resolved from the container.
  • That same object, the one that just came from the container, is then put back into the container.

I'm not sure why that's required. The thing you're requesting is already in the container, so you're just adding a second, static copy of it. My initial thought is that you probably don't need that line at all.

However, let's say you do need it or want it for some reason. You can do delayed resolution of an item by registering things as a lambda. I think that's something which may of the Update scenarios I've seen out there could use to work around the need for updating.

You could take this line...

updater.RegisterInstance(signalrResolver.Resolve<Microsoft.AspNet.SignalR.Infrastructure.IConnectionManager>());

...and change it to a lambda that uses the GlobalHost.DependencyResolver:

builder
  .Register(ctx => GlobalHost.DependencyResolver.GetService<IConnectionManager>())
  .As<IConnectionManager>()
  .SingleInstance();

The lambda doesn't execute until you actually try to resolve it, so by the time you need to get the object you'll have already set the static GlobalHost.DependencyResolver field and things will work out.

Again, though, verify that circular registration is necessary. If you can resolve it from the container already, you shouldn't need to re-add it.

@dornstein
Copy link

dornstein commented Jan 3, 2017

I think I might need to use Update() but I'm not sure yet. If there's an alternative, I'd love to know but if not then consider this another case for keeping it.

I'm building a platform (user ASP.NET) that supports pluggable "apps."  I'm using Autofac extensively currently for global and per-request scoped services.  Now I need to allow my plugged in apps to register components/services but I'm not sure how to do that. Note that the app lifetimes pretty much match the entire global lifetime of the web app.

Here's the challenge:

One of the current services is the singleton AppManager service which is responsible for loading each app and getting it started up.  The challenge is this: the AppManager of course has various dependencies that it needs injected in so the app manager can't be instantiated in order to load apps until Build() has already been run on the IContainer.  Only then can each app be given a chance to register its services.   But by then the container has been built.

I've seen that there is an Update() method for the container but there are strong reasons listed why using that should be avoided.  Because I have confidence that the app-based registration will happen relatively early in the startup of the whole platform maybe these cautions can be ignored (?), but I'm wondering if there is some better way.

How would you approach this in a way most consistent with best practices for Autofac?

@tillig
Copy link
Member Author

tillig commented Jan 3, 2017

There are a few ways I've seen things in these situations handled. These aren't the only ways, and it's not One Size Fits All, but mix and match as appropriate.

Keeping in mind the original workarounds and fix tips I already posted...

Consider Two Containers

If you are using DI during app startup and then also using it during the execution of the app, it may be that you need two containers: one for each stage in the app lifecycle.

I have this myself in several of my larger apps - a container that has services used to index plugins (the "assembly scanning" mechanism, logging, that sort of thing); and a second container into which runtime requirements are registered like the set of plugin assemblies, required common dependencies, and so on.

Don't Over-DI Bootstrap Items

It's good to use DI, but you can easily get into a chicken/egg situation where you try to resolve bootstrap items (like your application configuration system, logging that will run during application startup, and so on) out of the container... that you're trying to set up during app startup.

Don't do that.

If you look at many of the newer ASP.NET Core examples, you'll see a good pattern where app configuration, base logging, and other "bootstrap" elements are actually just directly instantiated or built. Those instances/factories can then later be registered with Autofac for use during runtime, but the initial construction proper isn't done out of Autofac.

Share Instances Across Containers

If you go with the two-container startup, you can always register the same instance of a thing (e.g., application configuration, logging factory, etc.) into two different containers. At that point it's effectively a singleton.

(You can also use Autofac modules to share registrations if you have bunches of them that need to be duplicated, though you'll get different instances of things so be aware.)

Lambdas, Lambdas, Lambdas

Truly, almost every container update I see in the wild could be worked around using a lambda registration. "I need to register XYZ based on the result of resolving ABC!" - do that with a lambda registration (as shown in the workarounds at the top).


Given there's not a One Size Fits All solution, I can't say "here's the step-by-step way you, in your very specific app, would handle all of these cases." But the above should help. The two-container approach is the first thing I thought of when you mentioned your issue.

Note: I copied these ideas to the top so they're not buried in the middle of this long chain.

@dornstein
Copy link

dornstein commented Jan 3, 2017 via email

@tillig
Copy link
Member Author

tillig commented Jan 3, 2017

Instead of passing around two containers, consider the "share instances across containers" idea...

var configuration = CreateTheConfigurationInstance();
builderForContainer1.RegisterInstance(configuration);
builderForContainer2.RegisterInstance(configuration);

You could also do something like...

builderForContainer2.Register(ctx => container1.Resolve<Something>());

That is, use a lambda to resolve something from one container using another container.

That latter snippet has a bad smell, in my opinion. I think sharing bootstrap object instances across containers is fine. Those instances may even be factories.

var loggingFactory = new LoggingFactory(configuration);
builderForContainer1.Register(ctx => loggingFactory.CreateLogger()).As<ILogger>();
builderForContainer2.Register(ctx => loggingFactory.CreateLogger()).As<ILogger>();

If you have stuff in your app startup container that you also want to use during runtime, then you really need them to be singletons (instances). The problem is that if they aren't then the dependencies they are using from the first container aren't consistent with the dependencies registered in the second container. That's the HandlerManager example I posted in the comment at the top. If you're registering an object in one container that's been resolved from another container... that has a bad smell to it.

Let me use an analogy: think about a circular build dependency. (I'm going off topic a bit here, but stick with me.)

Say you have two libraries (Library 1 and Library 2) and some custom build tasks. The custom build tasks are used to build both libraries... but they also depend on Library 1.

You could address that by building Library 1 without the tasks, saving a copy of that, building the build tasks, then re-building Library 1 and Library 2 using the built version of the tasks... but your build tasks are then using a stale/inconsistent version of Library 1. That's not cool.

The right way to handle that is to either stop building Library 1 with the same set of build tasks that depend on it... or split the stuff out of Library 1 that the build tasks need into, say, Library 0, and build Library 0 without the tasks and let Library 1 and 2 build with the tasks.

Bringing it back on topic:

It may seem like there's no problem with that initial circular dependency... and that's the "I don't care if my app's dependencies are inconsistent, I need to use Update()!" argument. (Or "...I'll just register one object that came from one container into another container!") But there is something wrong with the circular dependency - it needs to be unwound so things are consistent and expected - just like trying to over-DI or create complex containers-calling-containers really should be unwound.

It also may seem like there's no way to unwind the circular dependency... and that's the "It's too hard to change my design so I'll do the least work possible and make it work despite the complexity!" argument. But there is a way to unwind it by changing the problem definition. In the circular dependency analogy, splitting Library 1 up allows you to change the way things work; in the app startup vs. plugins scenario, consider what really needs to be shared. Is it actual object instances or is it registrations? Can you use Autofac modules to help you? Can you use lambdas or RegisterInstance to help? What about doing a few things manually outside the container?

How, exactly, to unwind that is going to be up to you. I can't really provide much beyond what I already have when faced with fairly sweeping generalities. I'd just say... if you see the solution getting really complicated ("this container resolves from that container which reads from some factory that...") then you may not have the right solution.

An example of a widely-used two-container setup with some delegation around it is ASP.NET Web API 2. In there you'll see that the application configuration object has a static Services collection (basically a container) against which application level services can be registered (like the action invoker). When the application needs something, it doesn't query the dependency resolver, it queries the services collection. If the services collection doesn't have the service, it falls back internally to the dependency resolver.

That comes with its own challenges since, at least in Web API 2, that doesn't support per-request services. In some cases things get incorrectly cached, too, so something that's registered "instance-per-dependency" will be held for the app lifetime and become a singleton.

A lot of things changed in ASP.NET Core, where most things do actually come from the container... but when you look at how things like middleware Invoke methods are executed, the parameters for Invoke are a much more manual resolution method. Same with parameters that get pushed into your Startup class methods.

I can't tell you if you need your own version of a Services collection like Web API 2... I can't tell you if you need to do a few things more manually to unwind that multi-container conundrum you describe... but hopefully there are a few ideas here you can use to get you out of the jam.

@nblumhardt
Copy link
Member

Loving this discussion, apologies for jumping in a bit late. I find myself leaning heavily towards making the registrations-in-child-container pattern a first-class concept, but then found myself wondering, will supporting registrations in child scopes interfere with any of the simplification/optimisation opportunities we're trying to open up?

(Also, FWIW, System.Composition is another immutable container implementation.)

@dornstein
Copy link

I'm starting with the idea of getting the minimal set of app-loading items up and running without DI. That seems simplest. I think that will probably work but if it doesn't I may try the multiple containers.

I might end up trying to anyway because I'm trying to decide if one app's registrations should be visible for another app -- there are pros and cons -- but if they need to be per-app then one global container plus one container per app is probably the only choice.

If I do end up with multiple containers, I would share instances for the singletons, but I'm really not sure how I'd deal with per-request scoped objects (yet).

[And thanks for being so responsive. This sort of plugin situation seems like it would be at least semi-common...]

@tillig
Copy link
Member Author

tillig commented Jan 3, 2017

Child lifetime scopes (another item mentioned in the top post) could also be a solution.

Create the container with the application startup stuff. Each plugin then gets its own child lifetime scope and treat those lifetime scopes "like containers" for the plugins. Or if it's a central container with multiple "apps" or whatever your terminology is... instead of passing around IContainer, use ILifetimeScope. You can add registrations to the child scope when it's created.

When building the child app scopes, instead of passing around a ContainerBuilder you could pass a List<Action<ContainerBuilder>> and register things that way...

var regActions = new List<Action<ContainerBuilder>>();
regActions.Add(b => b.RegisterInstance(something));
regActions.Add(b => b.RegisterType<SomeType>());

...and later just run those.

var appScope = container.BeginLifetimeScope(b => {
  foreach(var a in regActions)
  {
    a(b);
  }
});

This "treat a child lifetime scope as a container" thing is how the multitenant integration works.

@bc3tech
Copy link

bc3tech commented Jan 4, 2017

Update gets used by bots built on the  Microsoft Bot Framework as the BotBuilder SDK uses Autofac internally. If users wish to register additional types in to the container already present in the BotBuilder for use in other areas of their bot, ContainerBuilder + Update() was the way to do it.

Said a different way, the BotBuilder SDK exposes an IContainer to which users can, and often do, add additional registrations in to at startup (before any resolutions happen).

@tillig
Copy link
Member Author

tillig commented Jan 4, 2017

@bc3tech Interesting. We'll have to file an issue over there to see if we can get them to change their architecture. Thanks!

@brandonh-msft
Copy link

@tillig I've started an internal thread on this

@xperiandri
Copy link

I was in this same situation about OWIN and SignalR until I found a solutions

@aleksmelnikov
Copy link

aleksmelnikov commented Mar 20, 2018

I have done my example of container.update usage:
https://github.com/superriva/AUTOFAC_examples/tree/master/test2
A simple server build a container with the lib1 then make update the container with the lib2. The server works with assemblies, json configs and modules. I think it likes production tasks. Because I can make hot reload server logic.
The output is:

ContainerBuilder time (ms): 1.4322
ConfigurationBuilder time (ms): 18.7695
config.Build() time (ms): 64.5171
Create module time (ms): 0.3346
Register module time (ms): 2.0207
/at/c#/test2/lib1/bin/Debug/netcoreapp2.0/lib1.dll
Load time (ms): 52.61
Lib1
BUILD
Build or Update time (ms): 276.4384
Container work time (ms): 408.6262
________________________________
ContainerBuilder time (ms): 0.0086
ConfigurationBuilder time (ms): 0.279
config.Build() time (ms): 1.4935
Create module time (ms): 0.005
Register module time (ms): 0.0254
/at/c#/test2/lib2/bin/Debug/netcoreapp2.0/lib2.dll
Load time (ms): 0.716
Lib2
UPDATE
Build or Update time (ms): 9.3741
Container work time (ms): 11.3989

Build operation is expensive (276.4384ms), update operation is cheap (9.3741ms).
How I could make this without container.update (fast hot update)?
Thank you in advance.
In general, I think it's the best Autofac option. If developer uses this option then he understands what he does and can solve any problem with dependencies and others.

@vincentparrett
Copy link

Which overload do you use?
Update(IContainer)

I have a scenario where I create child lifetime scopes, and I need to inject that lifetimescope in a class, so I do this

    childscope = container.BeginLifetimeScope();
    var builder = new ContainerBuilder();
    builder.Register((ctx, p) =>
    {
      return new WcfUser(userName);
    }).As<IWcfUser>().SingleInstance();

    builder.RegisterInstance<ILifetimeScope>(childscope); <<<<< How to do this???
    builder.Update(childscope.ComponentRegistry);

Changing the IWcfUser registration part was easy using the action in the BeginLifetimeScope method, however it seems BeginLifetimeScope doesn't register itself, so what happens when try to resolve something (yes, I know it's an antipattern, it's unavoidable at the moment) that takes a lifetimescope, it get's the parent scope, and the further later stuff that was registered with the child scope doesn't resolve.

This particular issue could be worked around by registering the lifetimescope with itself.

@nblumhardt
Copy link
Member

@vincentparrett ILifetimeScope is already implicitly registered in the child scope; if you resolve ILifetimeScope, you always get the one that you're in. HTH!

@tillig
Copy link
Member Author

tillig commented Mar 21, 2018

@vincentparrett If you add an ILifetimeScope constructor parameter to an object it will be populated with the lifetime scope from which the consuming object is resolved. You should never have to register a scope with itself. You should also be able to register your additional user class as part of a lambda during BeginLifetimeScope.

@vincentparrett
Copy link

@nblumhardt it doesn't appear to be behaving that way. When I comment registering the childscope, I then get a resolution issue with the IWcfUser

None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'Continua.Session' can be invoked with the available services and parameters: Cannot resolve parameter 'Continua.Shared.Application.Services.IWcfUser wcfUser' of constructor 'Void .ctor(Autofac.ILifetimeScope, Continua.Shared.Data.IDatabaseFactory, Continua.Shared.Logging.ILogger, Continua.Shared.Application.Services.IWcfUser)'. Cannot resolve parameter 'Continua.ISessionPrincipal principal' of constructor 'Void .ctor(Autofac.ILifetimeScope, Continua.Shared.Data.IDatabaseFactory, Continua.Shared.Logging.ILogger, Continua.ISessionPrincipal)'.

This is because the IWcfUser is only registered in the child scope.

@vincentparrett
Copy link

@tillig I want to believe that, but the error I posted above was when I commented out registering the childscope with itself. The childscope is the only place where we register IWcfUser.

@tillig
Copy link
Member Author

tillig commented Mar 21, 2018

Cannot resolve parameter 'Continua.Shared.Application.Services.IWcfUser and Cannot resolve parameter 'Continua.ISessionPrincipal principal' are the keys there. It's not failing to resolve the lifetime scope, it's failing to resolve two other things. The code you posted above isn't enough to troubleshoot the issue (eg, we don't know where userName comes from) but it appears you may be looking at the wrong thing.

@vincentparrett
Copy link

My apologies, I thought I had reverted another experiement (also trying to remove the use of Update) and that was causing the above resolution error, that instance appears to be working fine now. I still have another few instances to tackle.. should I just remove my comments to avoid muddying waters?

@tillig
Copy link
Member Author

tillig commented Mar 21, 2018

No, it's fine. I'm glad things are working.

@vincentparrett
Copy link

@tillig I may have spoke too soon. I got the same error again, but after restarting I'm not able to reproduce it. I'll keep trying!

@tillig
Copy link
Member Author

tillig commented Mar 21, 2018

@vincentparrett If you run into more issues, consider StackOverflow. I'm there, many others are there, and it's a good place to post a bit more code and explanation so we can help you without taking this particular discussion thread into the weeds. If the SO answer results in a good summary of a use case where Update is needed, that would be a good follow up here.

@vincentparrett
Copy link

Will do... I'm back to the original error when removing the childscope registration so something is definitely amis.

@vincentparrett
Copy link

FWIW, the issue I am seeing only occurs when used with WCF. I created a simple console app to test the theory and in that case the ILifetimescope resolved was indeed correct, however when used with WCF, it's resolving the root container. I'm using a "copy" of the autofac.wcf code so will log a bug there once I figure out how to create a small reproducable case.

@Wiesenwischer
Copy link

What to do when using PRISM for WPF with Autofac as DI? It is not a problem to use the ContainerBuilder to register additional services etc in modules which are loaded at startup. But what can I do when a module is loaded on demand (cause the container has already been built up)?

@tillig
Copy link
Member Author

tillig commented Mar 29, 2018

@BadMadDev There is already an issue for this in Prism. It will require work that they may or may not choose to do. I recommend taking it up with the folks owning that project.

JuanAr added a commit to microsoft/botbuilder-dotnet that referenced this issue Apr 9, 2018
@evil-shrike
Copy link

Hi.

I'm struggling to implement a Autofac-based solution for building complex object graphs in my app. My issues seem to be related to the "immutable container" idea discussed here. So let me describe my use case. I apologies if it become too overloaded description. I'll try to keep it as clean as I can.

Context

The app is based on our own framework. That framework contains a notion of "XApplication". XApplication contains a set of subsystems. Examples of such subsystems are: Storage, Domain, Security, Reporting, and so on. Kind of enterprise line-of-business stuff.

An application based on the framework should define its structure in terms of XApplication and its subsystems. To do this it's provided with kind of a DSL builder - XApplicationBuilder.

Logically the process of an app initialization looks like:

            ContainerBuilder builder = new ContainerBuilder();
            XApplicationBuilder appBuilder = new XApplicationBuilder(builder);

            // 3. it's kind of user code - defining the structure of application
            new XApplicationFactory().Build(appBuilder);

            appBuilder.Apply();
            var container = builder.Build();
            var app = container.Resolve<IXApplication>();

"User code" means code of an application that uses the framework.
The app defines xapplication's structure in its XApplicationFactory using an XApplicationBuilder instance. Here's an example of such definition logic:

public void Build(XApplicationBuilder builder)
{
    builder
        // Storage
        .WithStorageSubsystem()
            .AddStorage(storageKind, conStrDef, conStrInit)
            .AddStorageSqlite("log")
                .AddConnection()
                    .AsFile(Path.Combine(Hosting.ContentRootPath, "log.db"))
                .End()
                .AddInitialization(XStorageInstanceInitializationBehavior.Update)
            .End()
            .WithDiagnostics().CommandExecuted(true).UsePerfCounters(false).End()
        .End()
         // Domain
        .WithDomainSubsystem()
            .WithModel()
                .LoadFrom(Path.Combine(Hosting.ContentRootPath, "Metadata", "metadata.xml"))
            .End()
            .AddLocalSessionFactory()
            .AddInstanceFactoryProvider<DomainObjects.Factories.FactoryProvider>()
            .WithScopes()
                .AddScope()
                    .WithSavePipeline()
                        .Step<SurveyOrganizerUpdateSessionStep>(UpdateSessionStage.BeforeDomainChecks)
                    .End()
                .End()
            .End()
        .End()
        // Security
        .WithSecuritySubsystem()
            .WithUserProvider<UserProvider>().End()
            .WithAzManager<AzManager>().UseUserPrivilegesAsActionPrivileges().End()
        .End()
        // DataSources
        .WithDataSourcesSubsystem()
            .AddMetadataSource(Path.Combine(Hosting.ContentRootPath, "Metadata", "DataSources","data-sources.xml"))
        .End();
}

Here the app defines Domain, Storage, Security and DataSources subsystems. Each subsystem definition contains some specific logic for that subsystem. Let's look at Domain subsystem definition. It says that the subsystem should load domain model from the xml-file, use "local session" (that talks to storage as opposite to remote session that call services), registers "InstanceFactory" - component that will create generated c# classes for types from model (like Department, User and so on), defines "scopes" - it's kind of pipelines inside session (during save and load).
Particular details aren't very important. I just wanted illustrate that it's kind a complex graph of objects.

Then user code calls XApplicationBuilder.Apply() where I should do something that will help to build a XApplication when user code ask for it in the subsequent var container = builder.Build(); and container.Resolve<IXApplication>().

As you can see till now we didn't see any Autofac specifics despise the fact that a ContainerBuilder was passed into XApplicationBuilder. In real app it won't be a standalone builder but a nested one:

            containerBuilder.RegisterBuildCallback(container =>
            {
                container.TryResolve(out IXApplicationFactory appFactory);
                container.TryResolve(out IEnumerable<IXApplicationFactoryAddon> addons);

                m_appScope = container.BeginLifetimeScope(nestedBuilder =>
                {
                    var appBuilder = new XApplicationBuilder(nestedBuilder);
                    appFactory.Build(appBuilder);

                    foreach (var addon in addons)
                    {
                        appBuilder.RegisterPreprocessor(addon.PreProcessApplication);
                    }
                });
                {
                }
            });

where containerBuilder is a Builder from ASP.NET Core app's Startup. But I'm afraid it's too overloaded for understanding so let's suppose it's just a ContainerBuilder.

So let's back to XApplicationBuilder. What the problem I have here and how it's related to "immutable container" idea.

Problem

It's hard to build everything as a single resolution. Like when we register everything via ContainerBuilder.RegisterType and resolve a root and the whole object tree is created.

Every subsystem has its own "assembler" - some kind of configuration logic. An subsystem can also have nested structure like "Storage Instances" for Storage subsystem or "Data Sources" for DataSources subsystem.

For example for Storage subsystem its assembler does the following:

	foreach (XStorageInstanceData storageInfo in configData.Storages)
	{
		XStorageInstance instance =
			XStorageInstanceCustomFactory.Instance.Create(context, storageInfo);

		subsystem.AddStorage(instance);
		if (storageInfo.IsDefault)
		{
			subsystem.SetDefaultStorage(storageInfo.Name);
		}
	}

I should tell what configData and configData.Storages are.
When user code defines app's structure via fluent-builder like WithStorageSubsystem().AddStorage() then the builder actually creates some object model. It's not final objects themselves and not Autofact's registrations (at least now), it's kind of DSL definitions describing what the app wants. We can call it as "application configuration". So configData above is the configuration object for Storage subsystem (created inside fluent-builder returned by WithStorageSubsystem).

Here you might say "hey, why do you need these assemblers, just register types in container in your builders". Assemblers are the legacy of the current approach that I'm trying to migrated from. It is based on Unity and ObjectBuilder (some internal part of Unity). Idea here is that every component has three participants: config-object, assembler and component - assembler takes config-object and builds component.

At first I tried to replace Unity with Autofac. But it's not easy. I have kind of drill-down building logic. Storage subsystem's assembler calls to its storages' assemblers and so on. At each level assemblers register a component being currently built before going down. For example storage subsystem's assembler registers the subsystem then calls to nested storage's assembler. This allows us in Storage's constructor accept all parent components.

But I can't do the same with Autofac as mixing of registration and resolution logic is forbidden.
So it's hard to come up with an alternative approach in "immutable container" world. I'd be appreciated for any hints. Thanks.

@tillig
Copy link
Member Author

tillig commented Apr 17, 2018

There's a lot to parse here and it may be that others can help you with your fairly complex situation. Sadly, while I'm happy to help unblock folks and I realize the challenging situation people are in... there are only two project owners and we're spread pretty thin between support, actual patching/dev on the project, and so on. As such, I can't really write the code for you. The guidance I would give you to start unblocking you is the same as the ideas I mentioned at the top of the discussion:

  • Don't over DI things. If you have configuration that needs to be read in order to generate application registrations, for example, consider just parsing the configuration outside the container.
  • Consider two containers. If you have to have a composable application bootstrapper, consider an application bootstrap container that is separate from the container that actually runs the application proper. For example, maybe you have a container that reads configuration and parses the user code and you use that bootstrap container to resolve the set of things that need to be registered for the application to run. Yes, this may mean you need to register a shared component into two containers. This is how ASP.NET Core does things as well as other common app frameworks.
  • Use lambda expressions. If there are things that need to be resolved based on, say, a read from a database value or a configuration entity that could change at runtime, use a lambda expression registration.

I see you went straight to container build callbacks and I don't think you need to go that low level.

It sounds like you were using Unity and you may be sort of new to Autofac, so I would recommend looking at our documentation to help you get ideas of how to get dynamic registrations in place:

@SlayersAlpha3
Copy link

My problem remains, simply enough, that my code isn't compiled until after application start. Its on-demand, and staggered. And yes, I can have a new Container per in-memory assembly as they are created, but to me, the container is supposed to be what contains. Instead I have to pool containers.

@evil-shrike
Copy link

Let me discuss a more focused problem .
Imagine a component that accept some kind of metadata. It can be loaded by the component from disk or supplied from user code. If such metadata contains some extensibility points where CLR types can be specified (think of plugins) then the component will want to create instances via DI (instead of Activator.CreateInstance). Surely, the component can use its own ContainerBuilder but it would be nice to attach a parent container as it can contain some shared services. The component cannot know everything about all services to explicitly re-register them all in the child container.
So it'd be very nice to have something this:

ComponentContext containerParent = ..
ContainerBuilder containerBuilder = new ContainerBuilder();
containerBuilder.RegisterSource(new ParentContainerSource(containerParent));

Does it make sense?
I found ExternalRegistrySource type that looks as what I need but it's internal.

@tillig
Copy link
Member Author

tillig commented Apr 24, 2018

@evil-shrike Absolutely - rolling your own registration source is a wonderful solution to providing dynamic registrations. That's how we handle things internally like IEnumerable<T> support or "Any Concrete Type Not Already Registered" ("ACTNARS"). There is documentation on that, though exactly what the source should (or shouldn't) provide is totally application dependent so it's not really something we can provide guidance on.

I suggested this very solution to the folks in the Prism project as one option for dynamically loadable modules.

If you try it, let us know how it goes. Perhaps you could create a NuGet package with something generically usable for plugin loading.

Here are some internal ones to get you started:

@nelsonlaquet
Copy link

I'm working on a new project and just ran into the deprecation warning that lead me to this issue.

Which overload do you use?
I'd like to use Update(IContainer)

Have you already resolved something from the container before calling Update?
Yes

What is the scenario?
Basically the whole .net core WebHost / HostBuilder deal. I'd like to register a bunch of services via .net core's own IOC API via the Configure_Thing_ extension methods, and then extend the configuration with autofac specific code before building the final container. I'd like the configuration of the container to depend on services that are injected via the container.

The reason why I discovered this whole issue was that I am writing a simple configuration abstraction. Basically, I want to have classes like:

[BindToConfiguration("Azure:ResourceManager")]
public class AzureResourceManagerOptions
{
	// properties/ctor here...
}

That will be scanned for, populated by the appropriate configuration via .net core's IConfiguration, then injected back into the container AsSelf/SingleInstance. To make matters more complicated, I do not use the built-in IConfiguration.Bind methods - instead, I use a custom service (called ObjectMachine) that gives me a lot more flexibility when populating these types from config. Specifically one feature of ObjectMachine is being able to instantiate/hydrate polymorphic types.

The way in which I implement polymorphic types is via another service called IPolyJsonTypes that is used as a central repository for how types are to be discriminated against. IPolyJsonTypes registers types that have this attribute attached to them:

[PolyJson(typeof(Document), "branch")]
public class BranchDocument : Document
{
    // properties/ctor here...
}

Both the BindToConfiguration and PolyJson attributes inherit from a base attribute called InfoProviderAttribute, which is scanned for during configuration and registers ITypeInfo objects in the container AsSelf/SingleInstance. The concrete implementation of IPolyTypes has a ctor parameter of IEnumerable<PolyJsonInfo> (PolyJsonInfo is provided by PolyJsonAttribute and implements ITypeInfo).

So: PolyJson provides PolyJsonInfo singletons into the container, and those are picked up by IPolyJsonTypes, which is used by ObjectMachine to properly convert from .net core's IConfiguration to my own DTOs.

So with that all out of the way, my solution was simple... Create an interface called IContainerConfigurator that is scanned for after all of the other configuration is done, and have its only method Configure(ContainerBuilder builder) get invoked against a new container builder, that then updates my main IContainer.

The code that would implement the above feature looks like:

[Inject(InjectionScope.Singleton, typeof(IContainerConfigurator))]
public class ConfigBinder : IContainerConfigurator
{
	private readonly IConfiguration config;
	private readonly ObjectMachine objects;
	private readonly IEnumerable<ConfigBinderInfo> binders;

	public ConfigBinder(IConfiguration config, ObjectMachine objects, IEnumerable<ConfigBinderInfo> binders = null)
	{
		this.config = config;
		this.objects = objects;
		this.binders = binders ?? Enumerable.Empty<ConfigBinderInfo>();
	}

	public void Configure(ContainerBuilder builder)
	{
		foreach (var binder in binders)
		{
			builder
				.RegisterInstance(GetConfig(binder.ConfigSection, binder.ConfigType))
				.As(((IPleaseDontDoThis) binder.ConfigType).ClrType)
				.SingleInstance();
		}
	}

	public T GetConfig<T>(string section) =>
		(T) GetConfig(section, SpoType.Get<T>());

	public object GetConfig(string sectionName, ISpoType type)
	{
		var section = config.GetSection(sectionName).ToToke();
		var converted = objects.Convert(section, type);
		if (!objects.IsAllGood(converted, out var errors))
			throw Spoception.InvalidOperation($"Could not convert config section {sectionName} to type {type.FullName}: {errors.FormatNameEqualsValue()}");

		return objects.WriteObject(converted, type);
	}
}

But of course that leaves me with this issue - how can IContainerConfigurators configure the container after it's built? I can't instantiate ConfigBinder without an ObjectMachine, which won't work unless it has an IPolyJsonTypes instance, which I can't create unless I expose my ITypeInfo objects outside of the context of a container. Basically causing me to unwind a large part of how this project has been thus far architected.

Two Containers: I don't want to use two containers because of how the Configure_Serivce_ extension methods work in the .net core world. I can't call ContainerBuilder.Populate twice because I'm using AutofacServiceProviderFactory with my generic hosts, which handles the call to .Populate for me. Also, this needs to work with both the new generic host builder, as well as asp.net core v2's WebHost - preferably with the same container initialization.

Two containers could possibly work if there was a way to either A) create two containers from a single ContainerBuilder or B) copy a container builder's registrations into a fresh container builder.

ILifetimeScope: this was the most promising thread, and may work for me. The biggest issue that happens here is my configuration objects from ConfigBinder get registered on the ILifetimeScope, but not the main container - which doesn't seem like a problem until a service registered directly on the root IContainer requests a configuration object that it cannot reach.

I suppose I could have services that depend on dynamically registered services be registered in the lifetime scope, with everything else being registered on the root container... But this feels kinda gross, and requires that you know more details up front about your dependencies.

So, basically, that's where I am now. Tomorrow I may look into how complicated it would be to not have to use autofac to instantiate the objects that further configure my container builder. I don't prefer this solution, however, as everything else falls so neatly into place with the single cavate of Update being deprecated.

@tillig
Copy link
Member Author

tillig commented Jan 27, 2020

With the release of 5.0 containers are truly immutable after build and ContainerBuilder.Update is entirely removed from the package. ContainerBuilder.Update was marked obsolete in November 2016, so it's been about two years advance notice that this was coming. We plan on adding some documentation based on the discussion in this issue to help people have the safety of an immutable container but with the dynamic behavior they desire.

For folks who are unable or unwilling to change their application architecture to support a pattern that does not require ContainerBuilder.Update, you may stay on 4.9.4, the final release of Autofac. Note that we will not be issuing any further fixes or features for that version, so you remain there in an unsupported state.

If you feel there is a behavior that is missing, we'd love to see an enhancement issue filed with more specifics on what you would like to see and how you envision that feature working within the scope of immutable containers: pseudo-code sample usage, a description of the feature in detail, things you've tried that aren't working, that sort of thing.

@tillig tillig closed this as completed Jan 27, 2020
@autofac autofac locked as resolved and limited conversation to collaborators Jan 27, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests