Загружать сборки со ссылками из подпапок во время выполнения - PullRequest
4 голосов
/ 07 февраля 2020

В настоящее время я работаю над проектом, который должен работать в качестве платформы для нескольких дополнений, которые должны быть загружены во время выполнения.

Мне поручено иметь следующую структуру в папке приложения:

  • 2 Каталоги с подпапками. Один с именем " / addons " для надстроек и один с именем " / ref " для любых дополнительных ссылок, которые эти надстройки могут использовать (например, System.Windows.Interactivity.dll)
  • Когда При выборе одной из надстроек в меню приложения приложение .dll должно быть загружено во время выполнения, и должна быть открыта предварительно заданная точка входа
  • Должны быть загружены все ссылки на вновь загруженную сборку. aswell.

Я знаю подпапку и имя файла при загрузке дополнения, поэтому я просто использую 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)
  • Most Надстройки по-прежнему имеют старый формат .csproj в net472
  • , подпапка ref также структурирована в подпапках (dev express, system, et c.), В то время как подпапка надстроек больше не имеет подпапок.

Ответы [ 2 ]

5 голосов
/ 10 февраля 2020

TL; DR;

Вы ищете AssemblyResolve событие AppDomain. Если вы загружаете все сборки плагинов в текущем домене приложения, вам нужно обработать событие для AppDomain.CurrentDomain и загрузить запрошенную сборку в обработчик событий.

Независимо от того, что Структура папок, которые у вас есть для ссылок, что вы должны сделать:

  • Получить все файлы сборки из папки плагинов
  • Получить все файлы сборки из папки ссылок (вся иерархия)
  • Обработайте AssemblyResolve из AppDomain.CurrentDomain и проверьте, доступно ли запрошенное имя сборки для файлов справочной папки, затем загрузите и верните сборку.
  • Для каждого файла сборки в папке плагинов найдите все типы и, если type реализует интерфейс вашего плагина, создает его экземпляр и вызывает его точку входа, например.

Пример

В этом Po C я загружаю все реализации IPlugin динамически во время выполнения из сборок в папке Plugins и после загрузки их и разрешения всех зависимостей во время выполнения я вызываю SayHello метод плагинов.

* 103 5 * Приложение, которое загружает плагины, не имеет никакой зависимости от плагинов и просто загружает их во время выполнения из следующей структуры папок:

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

Вы можете скачать или клонировать код:

0 голосов
/ 11 февраля 2020

Microsoft уже решила подобные проблемы с помощью своей инфраструктуры надстроек. Я бы порекомендовал посмотреть по их ссылке Пошаговое руководство. Создание расширяемого приложения . Ссылка содержит полное руководство по созданию расширяемого консольного приложения от начала до конца sh с различными пояснениями ios. Вы можете загрузить надстройки из указанных c папок. Я подозреваю, что это также решит проблемы, которые могут у вас возникнуть.

Он также решает проблемы обратной совместимости, такие как «Новый хост, старые надстройки», а также пользовательские надстройки.

Сценарий конвейера: новый хост, старые надстройки.

enter image description here

Этот конвейер также описан в Сценарий конвейера надстройки ios.

Так что для. Net Core у нас есть другой способ создания расширяемого приложения. Следующая статья Microsoft Создание. NET Базового приложения с плагинами предлагает использовать средство разрешения зависимостей сборки для плагинов с зависимостями.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...