c# - 在运行时加载带有子文件夹引用的程序集

标签 c# .net reflection .net-core .net-assembly

我目前正在开发一个项目,该项目应该作为多个附加组件的框架,应该在运行时加载。

我的任务是在我的应用程序文件夹中具有以下结构:

  • 2 个带有子文件夹的目录。一个名为“/addons”的附加组件和一个名为“/ref”的附加组件可能使用的任何附加引用(例如 System.Windows.Interactivity.dll )
  • 从应用程序的菜单中选择其中一个附加组件时,.dll 应该在运行时加载,并且应该打开预设的入口点
  • 还应加载新加载的程序集的所有引用。

我知道加载附加组件时的子文件夹和文件名,因此我只需使用 Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location))Path.Combine()构建 .dll 的路径,然后通过 Assembly.LoadFile() 加载它在使用 assembly.GetExportedTypes() 反射之前找到继承我的“EntryPointBase”的类,然后使用 Activator.CreateInstance() 创建它.

但是,一旦我的附加组件中有任何引用,就会出现 System.IO.FileNotFoundException定位引用将弹出 assembly.GetExportedTypes()

我构建了一个方法来加载所有引用的程序集,甚至使其递归地从引用中加载所有引用,如下所示:

public void LoadReferences(Assembly assembly)
{

  var loadedReferences = AppDomain.CurrentDomain.GetAssemblies();

  foreach (AssemblyName reference in assembly.GetReferencedAssemblies())
  {
    //only load when the reference has not already been loaded 
    if (loadedReferences.FirstOrDefault(a => a.FullName == reference.FullName) == null)
    {
      //search in all subfolders
      foreach (var location in Directory.GetDirectories(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location)))
      {
        //GetDirectoriesRecusrive searchs all subfolders and their subfolders recursive and 
        //returns a list of paths for all files found
        foreach (var dir in GetDirectoriesRecusrive(location))
        {

          var assemblyPath = Directory.GetFiles(dir, "*.dll").FirstOrDefault(f => Path.GetFileName(f) == reference.Name+".dll");
          if (assemblyPath != null)
          {
            Assembly.LoadFile(assemblyPath); 
            break; //as soon as you find a vald .dll, stop the search for this reference.
          }
        }
      }
    }
  }
}

并通过检查 AppDomain.CurrentDomain.GetAssemblies() 确保所有引用均已加载,但异常保持不变。

如果所有程序集都直接位于应用程序文件夹中,或者如果启动应用程序本身已经引用了插件的所有引用,则它可以工作。 这两种方式都不适合我的情况,因为对此文件系统的更高要求以及具有新引用的附加组件应该能够在不接触应用程序本身的情况下加载。

问题:

如何在没有 System.IO.FileNotFoundException 的情况下从一个子文件夹加载程序集并从另一个子文件夹加载它们的引用?

其他信息:

  • 应用程序采用新的 .csproj 格式并在 <TargetFrameworks>netcoreapp3.1;net472</TargetFrameworks> 上运行,尽管对 net472 的支持应该很快就会停止(目前仍在 net472 中调试)
  • 大多数附加组件在 net472 上仍然采用旧的 .csproj 格式
  • ref 子文件夹也由子文件夹(devexpress、system 等)构成,而 addon 子文件夹没有其他子文件夹。

最佳答案

TL;DR;

您正在寻找AssemblyResolve AppDomain 的事件。如果您正在加载当前应用程序域中的所有插件程序集,那么您需要处理 AppDomain.CurrentDomain 的事件并在事件处理程序中加载请求的程序集。

无论您有什么文件夹结构可供引用,您应该做的是:

  • 从插件文件夹中获取所有程序集文件
  • 从引用文件夹(整个层次结构)获取所有程序集文件
  • 处理AppDomain.CurrentDomainAssemblyResolve并检查请求的程序集名称是否是引用文件夹的可用文件,然后加载并返回程序集。
  • 对于插件文件夹中的每个程序集文件,获取所有类型,如果该类型实现您的插件接口(interface),则实例化它并调用其入口点。
<小时/>

示例

在此 PoC 中,我在运行时从 Plugins 文件夹中的程序集动态加载 IPlugin 的所有实现,在加载它们并在运行时解决所有依赖关系之后,我调用插件的 SayHello 方法。

加载插件的应用程序对插件没有任何依赖性,只是在运行时从以下文件夹结构加载它们:

enter image description here

这就是我为加载、解析和调用插件所做的事情:

var plugins = new List<IPlugin>();
var pluginsPath = Path.Combine(Application.StartupPath, "Plugins");
var referencesPath = Path.Combine(Application.StartupPath, "References");

var pluginFiles = Directory.GetFiles(pluginsPath, "*.dll", 
    SearchOption.AllDirectories);
var referenceFiles = Directory.GetFiles(referencesPath, "*.dll", 
    SearchOption.AllDirectories);

AppDomain.CurrentDomain.AssemblyResolve += (obj, arg) =>
{
    var name = $"{new AssemblyName(arg.Name).Name}.dll";
    var assemblyFile = referenceFiles.Where(x => x.EndsWith(name))
        .FirstOrDefault();
    if (assemblyFile != null)
        return Assembly.LoadFrom(assemblyFile);
    throw new Exception($"'{name}' Not found");
};

foreach (var pluginFile in pluginFiles)
{
    var pluginAssembly = Assembly.LoadFrom(pluginFile);
    var pluginTypes = pluginAssembly.GetTypes()
        .Where(x => typeof(IPlugin).IsAssignableFrom(x));
    foreach (var pluginType in pluginTypes)
    {
        var plugin = (IPlugin)Activator.CreateInstance(pluginType);
        var button = new Button() { Text = plugin.GetType().Name };
        button.Click += (obj, arg) => MessageBox.Show(plugin.SayHello());
        flowLayoutPanel1.Controls.Add(button);
    }
}

这就是结果:

enter image description here

您可以下载或克隆代码:

关于c# - 在运行时加载带有子文件夹引用的程序集,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60116804/

相关文章:

c# - 在 silverlight 和 WPF 中实现多语言应用程序的最佳方式

c# - 动态方法和 MethodAccessException

java - 使用 ClassLoader 和 Class.forName 加载类的区别

c# - 排序通用链表

c# - 结合 C# 和 javascript 代码

c# - 如何为新的 Microsoft.Asp.NET Identity (MVC5) 设置自定义架构

.net - 事件处理程序和内存泄漏

.net - 在非当前 AppDomain 上使用 DefineDynamicAssembly 动态创建程序集时出现异常

c# - Docker 中的 FileSystemWatcher 不会注意到对本地目录的更改

c# - 为什么我可以使用集合初始值设定项和来自另一个类的私有(private)集合访问?