在运行时使用子文件夹的引用加载程序集 [英] Load assemblies with references from subfolders at runtime

查看:76
本文介绍了在运行时使用子文件夹的引用加载程序集的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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




  • 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中弹出()



我构建了一种方法来加载所有引用的程序集,甚至使它可以递归地从引用中加载所有引用,例如: / p>

  public void LoadReferences(装配体)
{

var loadReferences = AppDomain.CurrentDomain。 GetAssemblies();

foreach(assembly.GetReferencedAssemblies()中的AssemblyName引用)
{
//仅在尚未加载引用时加载
if(loadedReferences.FirstOrDefault( a => a.FullName == reference.FullName)== null)
{
//搜索所有子文件夹
foreach(Directory.GetDirectories(Path.GetDirectoryName(System。 .Reflection.Assembly.GetEntryAssembly()。Location)))
{
// GetDirectoriesRecusrive递归搜索所有子文件夹及其子文件夹,而
//返回找到的所有文件的路径列表
foreach(GetDirectoriesRecusrive(location)中的var dir)
{

var assemblyPath = Directory.GetFiles(dir, * .dll)。FirstOrDefault(f => Path.GetFileName (f)== reference.Name +。dll);
if(assemblyPath!= null)
{
Assembly.LoadFile(assemblyPath);
休息时间; //一旦找到一个vald .dll,就停止搜索该引用。
}
}
}
}
}
}

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



如果所有程序集都直接位于应用程序文件夹中,或者启动应用程序本身已经引用了附加组件的所有引用,则它起作用。
两种方式都不适合我的情况,因为对此文件系统和带有新引用的附件的更高的需求应该能够加载而不会碰到应用本身。



问题:



如何从一个子文件夹加载程序集,而又从另一个子文件夹加载程序集的引用 System.IO.FileNotFoundException



其他信息:




  • 应用程序采用新的.csproj格式,并在< TargetFrameworks> netcoreapp3.1; net472< / TargetFrameworks>上运行。 ,尽管应该很快停止对net472的支持(目前仍在net472中进行调试)

  • 大多数附件在net472上仍然具有旧的.csproj格式

  • ref子文件夹也构造在子文件夹(devexpress,system等)中,而addon子文件夹没有其他子文件夹。


解决方案

TL; DR;



您在寻找



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

  var plugins = new List< IPlugin>(); 
var pluginsPath = Path.Combine(Application.StartupPath, Plugins);
varreferencesPath = 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)
返回Assembly.LoadFrom(assemblyFile);
抛出新的异常($’{name}’未找到);
};

foreach(pluginFiles中的var pluginFile)
{
var pluginAssembly = Assembly.LoadFrom(pluginFile);
var pluginTypes = pluginAssembly.GetTypes()
.Where(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.Controls.Add(button);
}
}

这是结果:





您可以下载或克隆代码:




I am currently working on a project which is supposed to work as a framework for several Add-Ons, which should be loaded at runtime.

I am tasked to have following structure in my application folder:

  • 2 Directories with subfolders. One named "/addons" for Add-Ons and one named "/ref" for any additonal references these addons might use (like System.Windows.Interactivity.dll)
  • When selecting one of the Add-Ons from a menu in the applicaiton, the .dll is supposed to be loaded at runtime and a pre-set entry point should be opened
  • All references of the newly loaded assembly should be loaded aswell.

I know the subfolder and filename when an Add-On is being loaded, so I simply use Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location)) and Path.Combine() to build a path to the .dll and then load it via Assembly.LoadFile() before using reflection with assembly.GetExportedTypes() to find the class that inherits for my 'EntryPointBase' and then create it with Activator.CreateInstance().

However, as soon as I have any references within my Add-On, an System.IO.FileNotFoundException targeting the reference will pop up at assembly.GetExportedTypes()

I built a method to load all referenced assemblies, even made it recursive to load all references from the references, like this:

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.
          }
        }
      }
    }
  }
}

and made sure all references are loaded in by checking AppDomain.CurrentDomain.GetAssemblies(), but the exception stays the same.

It works if either all assemblies are directly in the application folder, or if all references fro the addon are already referenced by the startup application itself. Both ways are not suitable for my case, because higher ups demand on this file system and Add-Ons with new references should be able to load without touching the appication itself.

Question:

How can I load assemblies from one subfolder and their references from another without a System.IO.FileNotFoundException?

Additional information:

  • Application is in the new .csproj format and runs on <TargetFrameworks>netcoreapp3.1;net472</TargetFrameworks>, although support for net472 should be ceased soon (currently still debugging in net472)
  • Most Add-Ons still have the old .csproj format on net472
  • the ref subfolder is sturctured in subfolders aswell (devexpress, system, etc.), while the addon subfolder has no further subfolders.

解决方案

TL;DR;

You are looking for AssemblyResolve event of the AppDomain. If you are loading all the plugin assemblies in the current app domain, then you need to handle the event for AppDomain.CurrentDomain and load the requested assembly in the event handler.

No matter what folder structure you have for references, what you should do is:

  • Get all assembly files form plugins folder
  • Get all assembly files from references folder (entire hierarchy)
  • Handle AssemblyResolve of AppDomain.CurrentDomain and check if the requested assembly name is available files of reference folder, then load and return assembly.
  • For each assembly file in plugins folder, get all types and if the type implements your plugin interface, instantiate it and call its entry point for example.

Example

In this PoC I load all implementations of IPlugin dynamically at run-time from assemblies in Plugins folder and after loading them and resolving all dependencies at run-time, I call SayHello method of plugins.

The application which loads plugins, doesn't have any dependency to plugins and just loads them at run-time from the following folder structure:

This is what I did for loading, resolving and calling the plugins:

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);
    }
}

And this is the result:

You can download or clone the code:

这篇关于在运行时使用子文件夹的引用加载程序集的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆