在运行时使用子文件夹的引用加载程序集

问题描述 投票:0回答:1

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

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

  • 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子文件夹没有其他子文件夹。
c# reflection .net-core .net-assembly
1个回答
0
投票

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

无论您要使用哪种文件夹结构作为参考,您都应该做的是:

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

这是我在示例中所做的:

AssemblyResolve

详细示例

在下面的示例中,我假设您具有以下解决方案结构:

  • PluginCore:包含AppDomain.CurrentDomain-无依赖性
  • DependencyAssembly1:包含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); MessageBox.Show(plugin.SayHello()); } } -无依赖性
  • DependencyAssembly2:包含IPlugin.cs-无依赖性
  • PluginAssembly1:包含Class1.cs-依赖于DependencyAssembly1,PluginCore
  • PluginAssembly2:包含Class2.cs-依赖于DependencyAssembly1,DependencyAssembly2:PluginCore
  • PluginSample:Contais Program.cs-取决于PluginCore

PluginCore

ClassLibrary项目。没有依赖性。仅包含Plugin1.cs

IPlugin.cs

Plugin2.cs

DependencyAssembly1

ClassLibrary项目。没有依赖性。仅包含IPlugin.cs

Class1.cs

using System;

namespace PluginCore
{
    public interface IPlugin
    {
        string SayHello();
    }
}

DependencyAssembly2

ClassLibrary项目。没有依赖性。仅包含Class1.cs

Class2.cs

using System;
using System.Diagnostics;

namespace DependencyAssembly1
{
    public class Class1
    {
        public void DoSomething()
        {
            Debug.WriteLine($"{nameof(Class1)}.{nameof(DoSomething)} called.");
        }
    }
}

PluginAssembly1

ClassLibrary项目。依赖于PluginCore,DependencyAssembly1。仅包含Class2.cs

Plugin1.cs

using System;
using System.Diagnostics;

namespace DependencyAssembly2
{
    public class Class2
    {
        public void DoSomething()
        {
            Debug.WriteLine($"{nameof(Class2)}.{nameof(DoSomething)} called.");
        }
    }
}

PluginAssembly2

ClassLibrary项目。依赖于PluginCore,DependencyAssembly1,DependencyAssembly2。仅包含Plugin1.cs

Plugin2.cs

using PluginCore;
using System;

namespace PluginAssembly1
{
    public class Plugin1 : IPlugin
    {
        public string SayHello()
        {
            var c1 = new DependencyAssembly1.Class1();
            c1.DoSomething();
            return $"Hello from {nameof(Plugin1)}";
        }
    }
}

PluginSample

一个ConsoleApplication项目。取决于Plugin2.cs。仅包含using PluginCore; using System; namespace PluginAssembly2 { public class Plugin2 : IPlugin { public string SayHello() { var c1 = new DependencyAssembly1.Class1(); var c2 = new DependencyAssembly2.Class2(); c1.DoSomething(); c2.DoSomething(); return $"Hello from {nameof(Plugin2)}"; } } }

Program.cs

PluginCore
最新问题
© www.soinside.com 2019 - 2024. All rights reserved.