{"action":"created","issue":{"url":"https://api.github.com/repos/autofac/Autofac/issues/811","repository_url":"https://api.github.com/repos/autofac/Autofac","labels_url":"https://api.github.com/repos/autofac/Autofac/issues/811/labels{/name}","comments_url":"https://api.github.com/repos/autofac/Autofac/issues/811/comments","events_url":"https://api.github.com/repos/autofac/Autofac/issues/811/events","html_url":"https://github.com/autofac/Autofac/issues/811","id":189461190,"number":811,"title":"Discussion: ContainerBuilder.Update Marked Obsolete","user":{"login":"tillig","id":1156571,"avatar_url":"https://avatars1.githubusercontent.com/u/1156571?v=3","gravatar_id":"","url":"https://api.github.com/users/tillig","html_url":"https://github.com/tillig","followers_url":"https://api.github.com/users/tillig/followers","following_url":"https://api.github.com/users/tillig/following{/other_user}","gists_url":"https://api.github.com/users/tillig/gists{/gist_id}","starred_url":"https://api.github.com/users/tillig/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tillig/subscriptions","organizations_url":"https://api.github.com/users/tillig/orgs","repos_url":"https://api.github.com/users/tillig/repos","events_url":"https://api.github.com/users/tillig/events{/privacy}","received_events_url":"https://api.github.com/users/tillig/received_events","type":"User","site_admin":false},"labels":[{"id":76369928,"url":"https://api.github.com/repos/autofac/Autofac/labels/question","name":"question","color":"cc317c","default":true}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":43,"created_at":"2016-11-15T17:52:04Z","updated_at":"2017-03-01T17:17:03Z","closed_at":null,"body":"In https://github.com/autofac/Autofac/commit/8a89e94ad2ffed0c10ac613b7015c11a56275c99 the `ContainerBuilder.Update` methods have been marked obsolete.\r\n\r\nThe 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.\r\n\r\n**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`.\r\n\r\n# If You Want to Keep ContainerBuilder.Update...\r\n\r\n**If you believe you need `ContainerBuilder.Update` please provide the following information with your comment - \"me too\" or \"I use it\" isn't enough.**\r\n\r\n- Which overload do you use? `Update(IContainer)`, `Update(IContainer, ContainerBuildOptions)`, or `Update(IComponentRegistry)`?\r\n- What is the scenario (use case) in which you are unable to pass the `ContainerBuilder` prior to container building?\r\n- When you use `Update`, have you already resolved something from the container before calling `Update`?\r\n\r\nWe 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.\r\n\r\n# Why ContainerBuilder.Update Was Marked Obsolete\r\n\r\nHere are some of the reasons why the use of `ContainerBuilder.Update` is generally bad practice and why we're looking at this.\r\n\r\n## Container Contents Become Inconsistent\r\n\r\nOnce you resolve something out of a built container, a lot of things get put in motion.\r\n\r\n- If it's a singleton, its dependency chain gets resolved and the singleton gets cached.\r\n- Anything marked `AutoActivate` or `IStartable` gets resolved.\r\n- Disposable items may be cached and/or tracked.\r\n\r\nIf 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.\r\n\r\nIn unit test form (with a little pseudocode)...\r\n\r\n```csharp\r\n[Fact]\r\npublic void InconsistentContainer()\r\n{\r\n  var builder = new ContainerBuilder();\r\n\r\n  // In this example, the \"HandlerManager\" takes a list of message handlers\r\n  // and does some work with them.\r\n  builder.RegisterType<HandlerManager>()\r\n         .As<IHandlerManager>()\r\n         .SingleInstance();\r\n\r\n  // Register a couple of handlers that the manager will use.\r\n  builder.RegisterType<FirstHandler>().As<IHandler>();\r\n  builder.RegisterType<SecondHandler>().As<IHandler>();\r\n\r\n  using (var container = builder.Build())\r\n  {\r\n    // The manager is resolved, which resolves all of the currently registered\r\n    // handlers, and is cached. This manager instance will have two handlers\r\n    // in it.\r\n    var manager = container.Resolve<IHandlerManager>();\r\n\r\n    // Update the container with a new handler.\r\n    var updater = new ContainerBuilder();\r\n    updater.RegisterType<ThirdHandler>().As<IHandler>();\r\n    updater.Update(container);\r\n\r\n    // The manager still only has two handlers... which is inconsistent with\r\n    // the set of handlers actually registered in the container.\r\n    manager = container.Resolve<IHandlerManager>();\r\n  }\r\n}\r\n```\r\n\r\n## `Update` Isn't Consistent With `Build`\r\n\r\nWhen you Build a container, a couple of things happen:\r\n\r\n- 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.\r\n- Startable components (`AutoActivate` and `IStartable`) are automatically resolved.\r\n\r\nWhen 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.\r\n\r\nWhy 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).\r\n\r\nChild 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).\r\n\r\nBasically, `Update` really isn't the same as `Build` and using it may not be doing 100% of the things you think it's doing.\r\n\r\n## Diagnostics and Optimizations Difficult to Implement\r\n\r\nWe 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.\r\n\r\nFurther, 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.\r\n\r\n# Why Not Just \"Fix\" `Update`?\r\n\r\nThe question that logically follows is... why not just \"fix `Update` so it behaves correctly?\"\r\n\r\n## Easier Said Than Done\r\n\r\nIf you think about what would _actually_ have to happen to make `Update` work \"correctly\" it includes...\r\n\r\n- Flush all caches of singletons, disposing of tracked disposables, so singletons can be regenerated with the updated container contents.\r\n- 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.)\r\n- Re-run all startable components so they are created with the new container contents.\r\n\r\n...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.\r\n\r\n## Locking a Container Isn't Unprecedented\r\n\r\nLooking 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.\r\n\r\nFor those that do - StructureMap, Ninject, and Windsor to name a few - it appears they actually do all the work I mentioned in \"easier said than done\" - flushing caches, rebuilding the whole container.\r\n\r\n# Possible Workarounds for `Update`\r\n\r\nInstead of using `ContainerBuilder.Update`, you may be able to...\r\n\r\n## Pass Around `ContainerBuilder` Instead of `IContainer`\r\n\r\nInstead 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.\r\n\r\n## Add Registrations to Child Scopes\r\n\r\nOccasionally 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:\r\n\r\n```csharp\r\nusing (var scope = container.BeginLifetimeScope(b =>\r\n  {\r\n    b.RegisterType<NewRegistration>();\r\n  })\r\n{\r\n  // The new registration is available in this scope.\r\n}\r\n```\r\n\r\nYou 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](https://github.com/autofac/Autofac.Multitenant) works - cached lifetime scopes per tenant.\r\n\r\n## Use Lambdas\r\n\r\nIf 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:\r\n\r\n```csharp\r\nbuilder.Register(ctx =>\r\n{\r\n  if (Environment.GetEnvironmentVariable(\"env\") == \"dev\")\r\n  {\r\n    return new DevelopmentService();\r\n  }\r\n  else\r\n  {\r\n    return new ProductionService();\r\n  }\r\n}).As<IMyService>();\r\n```\r\n\r\n## Use Configuration\r\n\r\nJust like with `web.config` transforms, you may choose to switch deployed [Autofac configuration files](http://autofac.readthedocs.io/en/latest/configuration/xml.html) based on an environment. For example, you may have a development configuration and a production configuration.\r\n\r\n## Use Modules\r\n\r\nIf you have a lot of registrations that need to change based on runtime, you can encapsulate that in a module.\r\n\r\n```csharp\r\npublic class MyModule : Module\r\n{\r\n  private readonly bool _isProduction;\r\n\r\n  public MyModule(bool isProduction)\r\n  {\r\n    this._isProduction = isProduction;\r\n  }\r\n\r\n  protected override void Load(ContainerBuilder builder)\r\n  {\r\n    if (this._isProduction)\r\n    {\r\n      builder.RegisterType<FirstProductionService>().As<IMyService>();\r\n      builder.RegisterType<SecondProductionService>().As<IOtherService>();\r\n    }\r\n    else\r\n    {\r\n      builder.RegisterType<FirstDevelopmentService>().As<IMyService>();\r\n      builder.RegisterType<SecondDevelopmentService>().As<IOtherService>();\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n## Short Term Fix\r\n\r\nIf your code uses `Update` and you want to keep using it for the time being, you can disable the warning just around that call.\r\n\r\n```csharp\r\n#pragma warning disable 612, 618\r\nbuilder.Update(container);\r\n#pragma warning restore 612, 618\r\n```"},"comment":{"url":"https://api.github.com/repos/autofac/Autofac/issues/comments/283406268","html_url":"https://github.com/autofac/Autofac/issues/811#issuecomment-283406268","issue_url":"https://api.github.com/repos/autofac/Autofac/issues/811","id":283406268,"user":{"login":"tillig","id":1156571,"avatar_url":"https://avatars1.githubusercontent.com/u/1156571?v=3","gravatar_id":"","url":"https://api.github.com/users/tillig","html_url":"https://github.com/tillig","followers_url":"https://api.github.com/users/tillig/followers","following_url":"https://api.github.com/users/tillig/following{/other_user}","gists_url":"https://api.github.com/users/tillig/gists{/gist_id}","starred_url":"https://api.github.com/users/tillig/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tillig/subscriptions","organizations_url":"https://api.github.com/users/tillig/orgs","repos_url":"https://api.github.com/users/tillig/repos","events_url":"https://api.github.com/users/tillig/events{/privacy}","received_events_url":"https://api.github.com/users/tillig/received_events","type":"User","site_admin":false},"created_at":"2017-03-01T17:17:03Z","updated_at":"2017-03-01T17:17:03Z","body":"I published [Autofac 4.4.0 to NuGet](https://www.nuget.org/packages/Autofac/4.4.0) which includes the `OnlyIf` and `IfNotRegistered` functions. [Documentation here.](http://autofac.readthedocs.io/en/latest/register/registration.html#conditional-registration)"}}
{"actor":{"display_login":"tillig"}}