在 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它们动态