Альтернатива для Assembly.LoadFile, Assembly.LoadFrom и Assembly.Load? - PullRequest
2 голосов
/ 22 января 2020

У меня две проблемы с минимальным, воспроизводимым примером, который имеет три целевых проекта. NET Core 3.1. Но я также хочу нацелиться на . NET Стандарт 2.0 .

Пример для приложения, которому нужно загрузить сборку во время выполнения и использовать предоставленную сборку.

Загруженная сборка ссылается на другой проект, который, в свою очередь, использует пакет NuGet.

Код

  • project Хост - это консольное приложение, которое не имеет ссылок на проекты и нет ссылок на пакеты. Он содержит файл Program.cs:
class Program
{
    static void Main(string[] args)
    {
        var featurePath = System.IO.Path.GetFullPath(@"..\..\..\..\Feature\bin\Debug\netcoreapp3.1\Feature.dll");
        var featureAssembly = System.Reflection.Assembly.LoadFile(featurePath);
        var featureType = featureAssembly.GetType("SomeFeature");
        var featureInstance = System.Activator.CreateInstance(featureType);
        featureType.InvokeMember("PrintText",
            System.Reflection.BindingFlags.InvokeMethod, null, featureInstance, new object[0]);
    }
}
  • . Project Feature - это библиотека классов, которая имеет ProjectReference для ..\Subfeature\Subfeature.csproj и содержит файл SomeFeature.cs:
  • Project Subfeature - это библиотека классов, которая имеет PackageReference для Newtonsoft.Json и содержит файл SomeSubfeature.cs:
public class SomeSubfeature
{
    public string GetText()
    {
        return Newtonsoft.Json.JsonConvert.SerializeObject("Some Text");
    }
}

Решенные проблемы

  • Первая решенная проблема заключается в том, что использованные сборки ссылаются на другой проект и / или используют пакеты. Assembly.LoadFrom загружает только запрошенную сборку. Сборка и пакеты указанного проекта не загружены. Это приводит к FileNotFoundException, поскольку Subfeature не может быть найден.

    Я мог бы решить эту проблему путем (1) замены Assembly.LoadFile(featurePath) на Assembly.LoadFrom(featurePath), так что другие необходимые библиотеки DLL в том же каталоге также могут быть загружены. И (2) путем создания библиотек DLL, которые копируются только в один и тот же каталог во время публикации, также копируются во время сборки, добавляя в Feature.csproj <PropertyGroup><CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies></PropertyGroup>.

  • . Вторая решенная проблема заключается в том, что приложение блокирует библиотеки DLL во время их загрузки. Это мешает мне развертывать более новые версии DLL, пока приложение еще работает. Выпуск новых версий становится громоздким, когда, скажем, приложение является частью размещенного приложения IIS. . NET Core больше не поддерживает загрузку теневой копии DLL.

    Я мог бы решить эту проблему путем (1) замены Assembly.LoadFile(featurePath) на Assembly.Load(System.IO.File.ReadAllBytes(featurePath)), чтобы требуемые библиотеки DLL загружались из байтов, которые были прочитаны ранее. погрузка. И (2) наличие FileWatcher перезагрузить приложение, если что-то происходит с файлом DLL в его каталоге.

Конечная проблема

Решение первой проблемы не совместимо с решением второй проблемы. Assembly.LoadFrom исправляет мою первую проблему. Assembly.Load исправляет мою вторую проблему. Но я не нашел альтернативы для Assembly.LoadFile, которая устраняет обе проблемы одновременно.

1 Ответ

1 голос
/ 26 января 2020

HAdd AssemblyResolve обработчик событий для AppDomain.CurrentDomain и обработка загрузки сборки, как при чтении всех байтов.

        const string location = @"..\..\..\..\Dependency\bin\Debug\netcoreapp3.1\";
        static void Main(string[] args)
        {
            var domain = AppDomain.CurrentDomain;
            domain.AssemblyResolve += Domain_AssemblyResolve;
            var type = Type.GetType("Dependency.DependentClass,Dependency, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
            var obj = Activator.CreateInstance(type);
            Console.WriteLine(obj.ToString());     
        }

        private static Assembly Domain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            var dll = $"{args.Name.Split(",")[0]}.dll";
            var path = Path.Combine(location, dll);
            var asm = Assembly.Load(File.ReadAllBytes(path));
            return asm;
        }

Примечание: необходимо указать полное имя для типа, иначе он будет выдавать ошибка. Например: "Dependency.DependentClass"

Сборка зависимостей имеет 2 зависимости: одна - Newtonsoft.Json пакет NuGet, другая - другой проект. Bar класс находится в другом проекте.

 public class DependentClass
    {
        public int MyProperty { get; set; }

        public string AnotherProperty { get; set; }

        public override string ToString()
        {
            return JsonConvert.SerializeObject(new Bar());
        }
    }

РЕДАКТИРОВАТЬ: После понижения голосов я снова посмотрел вышеупомянутое решение, и хотя оно работает, скорее всего, оно будет . net Framework решения, а не .net Core Таким образом, более . net Core подобное решение заключается в использовании AssemblyLoadContext.

Определение пользовательского контекста загрузки сборки.

public class CustomLoadContext : AssemblyLoadContext
    {
        private readonly AssemblyDependencyResolver resolver;

        public CustomLoadContext(string mainAssemblyToLoadPath) : base(isCollectible: true)
        {
            resolver = new AssemblyDependencyResolver(mainAssemblyToLoadPath);
        }


        protected override Assembly Load(AssemblyName name)
        {
            Console.WriteLine("Resolving : {0}",name.FullName);
            var assemblyPath = resolver.ResolveAssemblyToPath(name);
            if (assemblyPath != null)
            {
                return Assembly.Load(File.ReadAllBytes(assemblyPath));
            }

            return Assembly.Load(name);
        }
    }

Загрузите сборку и дайте пользовательскому контексту загрузчика загрузить ее зависимости.

        const string location = @"..\..\..\..\Dependency\bin\Debug\netcoreapp3.1\";

        static void Main(string[] args)
        {
            var fullPath = Path.GetFullPath(location + "Dependency.dll");
            var clx = new CustomLoadContext(fullPath); // initialize custom context
            var asm = clx.LoadFromStream(new MemoryStream(File.ReadAllBytes(fullPath))); // load your desired assembly
            var ins = asm.CreateInstance("Dependency.DependentClass");  
            Console.WriteLine(ins.ToString());

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