The short answer here is, no, you can't update the container. As of Autofac 5.x, the container is immutable. Which is to say, you can't modify the contents once it's built.
The documentation explains why this is actually a good thing. One example: Let's say you have a singleton that keeps a list of all the currently enabled plugins. If you enable a new plugin, that singleton needs to be totally disposed and rebuilt. But now what if you have some sort of other manager class that uses the singleton? It also needs to be rebuilt so it has the new singleton that has the updated list of plugins.
Changing the container has a huge trickle-down effect like this on a lot of things.
Instead of trying to use Autofac as the feature flag or enabled/disabled mechanism, I'd recommend updating your design such that all the plugins available are loaded but possibly they don't actually get used unless they're enabled.
There are lots of ways to do that. I won't list literally all of them, but here are a couple of ideas to get you started. Also, these are skeleton ideas, you'll have to fill in some of the code gaps. It won't be copy/paste... I unfortunately don't have that kind of time. :(
Idea 1: Singleton Plugins, Name and Enabled Properties
You could define a plugin interface to be something like:
public interface IPlugin
{
string Name { get; set; }
bool Enabled { get; set; }
}
In your plugin class, Enabled
could default to false
and be turned on by, say, a configuration lookup or a database call.
public class PluginManager
{
private readonly IEnumerable<IPlugin> _plugins;
public PluginManager(IEnumerable<IPlugin> plugins)
{
// All the IPlugin plugins get registered in the
// container and default to Enabled=false.
this._plugins = plugins;
}
public void InitializePlugins()
{
// Get the list of enabled plugins from, like,
// a database or something.
var enabledNames = this.LoadEnabledPluginNames();
var enabledPlugins = this._plugins.Where(p => enabledNames.Contains(p.Name));
foreach(var plugin in enabledPlugins)
{
plugin.Enabled = true;
}
}
// Things that need the list of plugins should
// get them from this property in the manager.
public IEnumerable<IPlugin> EnabledPlugins
{
get {
return this._plugins.Where(p => p.Enabled);
}
}
// The manager should probably also be where
// you enable and disable plugins during the
// execution of the program so there's one place
// to both update the database/configuration AND
// mark the associated IPlugin as enabled or disabled.
}
Idea 2: Use Lazy<T>
If the problem is that the plugins are somehow "expensive to enable" or something, Autofac automatically supports use of Lazy<T>
to make resolution deferred.
For example, instead of resolving IEnumerable<IPlugin>
, resolve IEnumerable<Lazy<IPlugin>>
. Look up the list of what's enabled/disabled, and only resolve the ones that are enabled.
Note that since you won't have resolved objects to query names, you might have to change how you register them so there's metadata available beforehand. Easy enough with metadata.
var cb = new ContainerBuilder();
cb.RegisterType<PluginOne>().As<IPlugin>().WithMetadata("name", "one");
cb.RegisterType<PluginTwo>().As<IPlugin>().WithMetadata("name", "two");
cb.RegisterType<PluginManager>().SingleInstance();
The plugin manager that coordinates config/database with the list of enabled plugins will need to use Meta<T>
(metadata interrogation) and Lazy<T>
(deferred instantiation) together.
public void PluginManager
{
private readonly void IEnumerable<Meta<Lazy<IPlugin>>> _allPlugins;
private void IEnumerable<IPlugin> _enabledPlugins = Enumerable.Empty<IPlugin>();
public void PluginManager(IEnumerable<Meta<Lazy<IPlugin>>> plugins)
{
// ALL the plugins, but nothing is actually resolved/instantiated yet.
this._allPlugins = plugins;
}
public void InitializePlugins()
{
// Get the list of enabled plugins from, like,
// a database or something.
var enabledNames = this.LoadEnabledPluginNames();
// Names come from metadata now because you don't
// have resolved instances yet! Once you find the
// enabled ones, calling Meta<T>.Value will get you
// the Lazy<T>, and Lazy<T>.Value gets you the
// resolved plugin. If you ever call Lazy<T>.Value
// again, it won't re-resolve, it gives you the same
// instance. It's a one-time shot, so if you DISABLE
// a plugin, there's no "dispose" or "unload" of
// the plugins; you just would stop returning them
// in the list of enabled plugins.
this._enabledPlugins = this._allPlugins
.Where(p => enabledNames.Contains(p.Metadata["name"]))
.Select(p => p.Value.Value)
.ToArray();
}
// Things that need the list of plugins should
// get them from this property in the manager.
public IEnumerable<IPlugin> EnabledPlugins
{
get {
return this._enabledPlugins;
}
}
}
These are Just Ideas
There are possibly other things you could throw in here.
Like, maybe if you absolutely need to dispose of disabled plugins you need to resolve them in a lifetime scope. The plugin manager could hold onto that lifetime scope until something changes (enable/disable) and dispose/rebuild the scope with the set of enabled plugins to clean things up. Downside here is you'd also be throwing away all the already enabled plugins just to re-create them. Maybe that's OK.
Maybe instead of registering plugins at the container level the plugin manager registers them in its own nested lifetime scope so other classes can't just resolve a list of plugins, they must get them from the manager that tracks them as enabled or not.
This could go on for a while. There are a lot of ideas.
But, again, the container is immutable, so you can't register/deregister them on the fly.