C# 在运行时加载包含子文件夹引用的程序集

C# 在运行时加载包含子文件夹引用的程序集,c#,.net,reflection,.net-core,.net-assembly,C#,.net,Reflection,.net Core,.net Assembly,我目前正在做一个项目,该项目应该作为几个附加组件的框架,应该在运行时加载 我的任务是在我的应用程序文件夹中具有以下结构: 2个带有子文件夹的目录。一个名为“/addons”的用于加载项,另一个名为“/ref”的用于这些加载项可能使用的任何附加引用(如System.Windows.Interactivity.dll) 从应用程序的菜单中选择一个加载项时,应在运行时加载.dll,并打开预设的入口点 还应加载新加载程序集的所有引用 加载加载项时,我知道子文件夹和文件名,因此我只需使用Path.Ge

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

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

  • 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()
创建它

但是,只要我的加载项中有任何引用,就会在
assembly.GetExportedTypes()处弹出一个针对该引用的
System.IO.FileNotFoundException

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

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()
确保加载了所有引用,但异常保持不变

如果所有程序集都直接位于应用程序文件夹中,或者启动应用程序本身已经引用了该加载项的所有引用,则该方法有效。 这两种方法都不适合我的情况,因为更高层次的ups对该文件系统的要求和具有新引用的附加组件应该能够在不接触应用程序本身的情况下加载

问题: 如果没有
System.IO.FileNotFoundException
,如何从一个子文件夹加载程序集以及从另一个子文件夹加载它们的引用?

其他信息:

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

    • TL;博士

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

      无论引用的文件夹结构如何,您都应该执行以下操作:

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

      例子 在这个PoC中,我在运行时从
      Plugins
      文件夹中的程序集动态加载
      IPlugin
      的所有实现,在加载它们并在运行时解析所有依赖项之后,我调用插件的
      SayHello
      方法

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

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

      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);
          }
      }
      
      var plugins=newlist();
      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(referencepath,*.dll),
      SearchOption.AllDirectories);
      AppDomain.CurrentDomain.AssemblyResolve+=(对象,参数)=>
      {
      var name=$“{new AssemblyName(arg.name).name}.dll”;
      var assemblyFile=referenceFiles.Where(x=>x.EndsWith(名称))
      .FirstOrDefault();
      if(assemblyFile!=null)
      返回Assembly.LoadFrom(assemblyFile);
      抛出新异常($“{name}”未找到);
      };
      foreach(pluginfles中的var pluginfle)
      {
      var pluginAssembly=Assembly.LoadFrom(pluginFile);
      var pluginTypes=pluginAssembly.GetTypes()
      其中(x=>typeof(IPlugin).IsAssignableFrom(x));
      foreach(pluginTypes中的var pluginType)
      {
      var plugin=(IPlugin)Activator.CreateInstance(pluginType);
      var button=new button(){Text=plugin.GetType().Name};
      按钮。单击+=(obj,arg)=>MessageBox.Show(plugin.SayHello());
      flowLayoutPanel1.控件.添加(按钮);
      }
      }
      
      这就是结果:

      您可以下载或克隆代码:

      • 克隆
      • 下载

      微软已经使用他们的插件框架解决了这类问题。我建议你看看他们的链接。该链接包含一个完整的演练,以从头到尾创建一个可扩展的控制台应用程序,并解释了各种senarios。您可以从特定文件夹加载加载项。我怀疑这也会解决你可能遇到的问题

      它还处理向后兼容性问题,例如“新主机、旧加载项”以及自定义加载项

      管道场景:新主机、旧加载项

      这条管道