在 ASP.NET 核心中在运行时扩展 Autofac IoC 容器配置
Extend Autofac IoC container configuration at runtime in ASP.NET Core
我有一个 ASP.NET Core 5 MVC 应用程序,我使用 Autofac 作为我的 IoC 容器。为了将 Autofac 与 ASP.NET Core 集成,我使用了 Autofac.Extensions.DependencyInjection 包。我正在寻找一种从动态加载的程序集中注册服务的方法。我知道我可以在应用程序启动时执行此操作,但我需要在稍后的某个时间点执行此操作。例如。用户进入管理页面并启用某些模块。在 HTTP 请求处理期间,我想加载与该模块对应的程序集并从该程序集注册服务。你知道怎么做吗?
这里的简短回答是,不,您不能更新容器。 As of Autofac 5.x, the container is immutable.也就是说,内容一旦构建就不能修改了
documentation explains why this is actually a good thing。一个例子:假设你有一个单例,它保存了一份所有当前启用的插件的列表。如果启用新插件,则需要完全处理并重建该单例。但是现在,如果您有某种其他使用单例的管理器 class 怎么办?它还 需要重建,以便它具有包含更新的插件列表的新单例。
改变容器对很多事情都有像这样的巨大涓滴效应。
与其尝试将 Autofac 用作功能标志或 enabled/disabled 机制,我建议更新您的设计,以便 已加载 但可能它们实际上 不被使用 除非它们被启用。
有很多方法可以做到这一点。我不会字面上列出所有这些,但这里有一些想法可以帮助您入门。此外,这些是骨架想法,您必须填补一些代码空白。不会 copy/paste... 不幸的是我没有那种时间。 :(
想法 1:单例插件、名称和启用的属性
您可以将插件接口定义为:
public interface IPlugin
{
string Name { get; set; }
bool Enabled { get; set; }
}
在您的插件 class 中,Enabled
可以默认为 false
并通过配置查找或数据库调用打开。
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.
}
思路二:使用Lazy<T>
如果问题是插件在某种程度上“启用起来很昂贵”或其他原因,Autofac 会自动支持 use of Lazy<T>
以延迟解决。
例如,不解析 IEnumerable<IPlugin>
,而是解析 IEnumerable<Lazy<IPlugin>>
。查找 enabled/disabled 的列表,仅解析已启用的列表。
请注意,由于您不会解析对象以查询名称,因此您可能必须更改注册它们的方式,以便事先提供可用的元数据。 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();
协调 config/database 与已启用插件列表的插件管理器将需要同时使用 Meta<T>
(元数据查询)和 Lazy<T>
(延迟实例化)。
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;
}
}
}
这些只是想法
您可能还可以在此处添加其他内容。
就像,也许如果你绝对需要处理禁用的插件,你需要在生命周期范围内解决它们。插件管理器可以保留该生命周期范围,直到发生某些变化 (enable/disable) 并且 dispose/rebuild 范围与启用的插件集一起清理。这里的缺点是您还会丢弃所有已经启用的插件,只是为了重新创建它们。也许这没关系。
也许插件管理器不是在容器级别注册插件,而是在它自己的嵌套生命周期范围内注册它们,所以其他 classes can't 只是解析一个列表的插件,他们必须从跟踪它们是否启用的管理器中获取它们。
这可能会持续一段时间。有很多想法。
但是,容器是不可变的,所以你不能register/deregister它们动态。
我有一个 ASP.NET Core 5 MVC 应用程序,我使用 Autofac 作为我的 IoC 容器。为了将 Autofac 与 ASP.NET Core 集成,我使用了 Autofac.Extensions.DependencyInjection 包。我正在寻找一种从动态加载的程序集中注册服务的方法。我知道我可以在应用程序启动时执行此操作,但我需要在稍后的某个时间点执行此操作。例如。用户进入管理页面并启用某些模块。在 HTTP 请求处理期间,我想加载与该模块对应的程序集并从该程序集注册服务。你知道怎么做吗?
这里的简短回答是,不,您不能更新容器。 As of Autofac 5.x, the container is immutable.也就是说,内容一旦构建就不能修改了
documentation explains why this is actually a good thing。一个例子:假设你有一个单例,它保存了一份所有当前启用的插件的列表。如果启用新插件,则需要完全处理并重建该单例。但是现在,如果您有某种其他使用单例的管理器 class 怎么办?它还 需要重建,以便它具有包含更新的插件列表的新单例。
改变容器对很多事情都有像这样的巨大涓滴效应。
与其尝试将 Autofac 用作功能标志或 enabled/disabled 机制,我建议更新您的设计,以便 已加载 但可能它们实际上 不被使用 除非它们被启用。
有很多方法可以做到这一点。我不会字面上列出所有这些,但这里有一些想法可以帮助您入门。此外,这些是骨架想法,您必须填补一些代码空白。不会 copy/paste... 不幸的是我没有那种时间。 :(
想法 1:单例插件、名称和启用的属性
您可以将插件接口定义为:
public interface IPlugin
{
string Name { get; set; }
bool Enabled { get; set; }
}
在您的插件 class 中,Enabled
可以默认为 false
并通过配置查找或数据库调用打开。
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.
}
思路二:使用Lazy<T>
如果问题是插件在某种程度上“启用起来很昂贵”或其他原因,Autofac 会自动支持 use of Lazy<T>
以延迟解决。
例如,不解析 IEnumerable<IPlugin>
,而是解析 IEnumerable<Lazy<IPlugin>>
。查找 enabled/disabled 的列表,仅解析已启用的列表。
请注意,由于您不会解析对象以查询名称,因此您可能必须更改注册它们的方式,以便事先提供可用的元数据。 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();
协调 config/database 与已启用插件列表的插件管理器将需要同时使用 Meta<T>
(元数据查询)和 Lazy<T>
(延迟实例化)。
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;
}
}
}
这些只是想法
您可能还可以在此处添加其他内容。
就像,也许如果你绝对需要处理禁用的插件,你需要在生命周期范围内解决它们。插件管理器可以保留该生命周期范围,直到发生某些变化 (enable/disable) 并且 dispose/rebuild 范围与启用的插件集一起清理。这里的缺点是您还会丢弃所有已经启用的插件,只是为了重新创建它们。也许这没关系。
也许插件管理器不是在容器级别注册插件,而是在它自己的嵌套生命周期范围内注册它们,所以其他 classes can't 只是解析一个列表的插件,他们必须从跟踪它们是否启用的管理器中获取它们。
这可能会持续一段时间。有很多想法。
但是,容器是不可变的,所以你不能register/deregister它们动态。