Как загрузить сборки, расположенные в папке в .net-приложении консоли ядра
Я создаю консольное приложение на платформе .Net Core и задаюсь вопросом, как загружать сборки (файлы DLL) и создавать экземпляры классов с использованием динамических функций С#? Кажется, это сильно отличается от .Net 4.X, и он не документирован...
Например, скажем, у меня есть библиотека классов (.Net Core), и у нее есть только один класс:
namespace MyClassLib.SampleClasses
{
public class Sample
{
public string SayHello(string name)
{
return $"Hello {name}";
}
public DateTime SayDateTime()
{
return DateTime.Now;
}
}
}
Таким образом, имя файла dll будет MyClassLib.dll
и его расположение находится в /dlls/MyClassLib.dll
.
Теперь я хочу загрузить это в простое консольное приложение (.Net Core) и создать экземпляр класса Sample
и вызвать методы с использованием динамических функций С# в следующем консольном приложении:
namespace AssemblyLoadingDynamic
{
public class Program
{
public static void Main(string[] args)
{
// load the assembly and use the classes
}
}
}
Примечание:
По .Net Core я имею в виду версию RC2.
Ответы
Ответ 1
Не уверен, что это лучший способ сделать это, но вот что я придумал:
(Проверено только на .Net Core RC2 - Windows)
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyModel;
namespace AssemblyLoadingDynamic
{
public class Program
{
public static void Main(string[] args)
{
var asl = new AssemblyLoader();
var asm = asl.LoadFromAssemblyPath(@"C:\Location\Of\" + "SampleClassLib.dll");
var type = asm.GetType("MyClassLib.SampleClasses.Sample");
dynamic obj = Activator.CreateInstance(type);
Console.WriteLine(obj.SayHello("John Doe"));
}
public class AssemblyLoader : AssemblyLoadContext
{
// Not exactly sure about this
protected override Assembly Load(AssemblyName assemblyName)
{
var deps = DependencyContext.Default;
var res = deps.CompileLibraries.Where(d => d.Name.Contains(assemblyName.Name)).ToList();
var assembly = Assembly.Load(new AssemblyName(res.First().Name));
return assembly;
}
}
}
}
MyClassLib.SampleClasses
- это пространство имен, а Sample
- это имя класса aka.
При выполнении, он попытается загрузить SampleClassLib.dll
скомпилированную библиотеку классов в память и даст моему консольному приложению доступ к MyClassLib.SampleClasses.Sample
(посмотрите на вопрос), тогда мое приложение вызывает метод SayHello()
и передает "John Doe" в качестве названия, поэтому программа печатает:
"Hello John Doe"
Быстрое примечание:
Переопределение для метода Load
не имеет значения, поэтому вы можете просто заменить его содержимое на throw new NotImplementedException()
, и оно не должно влиять на все, о чем мы заботимся.
Ответ 2
В настоящее время работает против netcoreapp1.0
, вам на самом деле не нужно идти в меру реализации своего собственного AssemblyLoader
. Существует Default
, который работает просто отлично. (Следовательно, @VSG24 упоминает, что Load
ничего не делает).
using System;
using System.Runtime.Loader;
namespace AssemblyLoadingDynamic
{
public class Program
{
public static void Main(string[] args)
{
var myAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(@"C:\MyDirectory\bin\Custom.Thing.dll");
var myType = myAssembly.GetType("Custom.Thing.SampleClass");
var myInstance = Activator.CreateInstance(myType);
}
}
}
с project.json
похожим на:
{
"version": "1.0.0-*",
"buildOptions": {
"emitEntryPoint": true
},
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.1"
},
"System.Runtime.Loader": "4.0.0"
},
"frameworks": {
"netcoreapp1.0": {
"imports": "dnxcore50"
}
}
}
Ответ 3
Спасибо за ваш обмен. Он также работает с Net Core 1.0. Если вашей сборке нужны другие сборки на одном пути, вы можете использовать образец кода ниже.
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyModel;
public class AssemblyLoader : AssemblyLoadContext
{
private string folderPath;
public AssemblyLoader(string folderPath)
{
this.folderPath = folderPath;
}
protected override Assembly Load(AssemblyName assemblyName)
{
var deps = DependencyContext.Default;
var res = deps.CompileLibraries.Where(d => d.Name.Contains(assemblyName.Name)).ToList();
if (res.Count > 0)
{
return Assembly.Load(new AssemblyName(res.First().Name));
}
else
{
var apiApplicationFileInfo = new FileInfo($"{folderPath}{Path.DirectorySeparatorChar}{assemblyName.Name}.dll");
if (File.Exists(apiApplicationFileInfo.FullName))
{
var asl = new AssemblyLoader(apiApplicationFileInfo.DirectoryName);
return asl.LoadFromAssemblyPath(apiApplicationFileInfo.FullName);
}
}
return Assembly.Load(assemblyName);
}
}
Не забудьте добавить следующие зависимости в ваш файл project.json
:
"System.Runtime.Loader"
"Microsoft.Extensions.DependencyModel"
Ответ 4
Используя .net core 1.1/standard 1.6, я обнаружил, что AssemblyLoader недоступен, а
AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath)
дал мне ошибку "Не удалось загрузить файл или сборку xxx".
Наконец, это решение ниже работало для меня - просто добавив шаг для получения объекта AssemblyName. Надеюсь, это поможет любому, кто застрял:
var assemblyName = AssemblyLoadContext.GetAssemblyName(assemblyPath);
var assembly = Assembly.Load(assemblyName);
Ответ 5
@Rob, единственный способ, которым я мог бы построить ваш пример, - это изменить тип myInstance на динамический.
Оставляя тип var, он позволяет создавать код, но как только я попытаюсь использовать метод из загруженной сборки во время выполнения, я получаю ошибки компилятора, такие как myInstance, не содержит метода X. Я новичок в этом, но разметка типа динамическая, кажется, имеет смысл. Если тип загружается во время выполнения, то как проверить компилятор myInstance будет содержать метод X или prop Y? Набрав myInstance как динамический, я считаю, что вы удаляете проверку компилятора, и, таким образом, я мог бы получить пример для создания и запуска просто отлично. Не уверен, что это на 100% правильный путь (я не знаю достаточно, и вы можете посоветовать, что там проблема с использованием динамического?), Но это единственный способ заставить меня работать, не прибегая к проблемам создания собственных AssemblyLoader (как вы правильно указываете).
Итак...
using System;
using System.Runtime.Loader;
namespace TestApp
{
class Program
{
static void Main(string[] args)
{
var myAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(@"C:\Documents\Visual Studio 2017\Projects\Foo\Foo\bin\Debug\netcoreapp2.0\Foo.dll");
var myType = myAssembly.GetType("Foo.FooClass");
dynamic myInstance = Activator.CreateInstance(myType);
myInstance.UpperName("test");
}
}
}
Надеюсь, что это помогает кому-то быть новым, мне потребовались годы, чтобы точно определить, почему myInstance как var не имел метода X и т.д. Doh!
Ответ 6
Я много в этом разбираюсь, пробовал подход DependencyContext... Он хорошо работает, но имеет некоторые ограничения и отличается от стандартного разрешения сборки, которое есть в приложении dotnet c++, которое запускает ваше приложение. Вы должны выполнить сопоставление имен вручную, и если ваше хост-приложение опубликовано, у вас не будет пути поиска для папки nuget, которая является проблемой (решаемой), если ваша дочерняя сборка находится в режиме отладки и использует nuget...
Итак, вот еще одно решение: если приложение (assemblyA), загружающее сборку вручную (assemblyB), не имеет зависимостей (или не имеет конфликтующих зависимостей с assemblyB), я предлагаю обмануть и по умолчанию использовать разрешение сборки AssemblyB. Для dotnet.exe есть скрытый гем, который позволяет вам загрузить файл deps по вашему выбору, чтобы вы могли сделать что-то вроде этого:
dotnet exec --depsfile pathToAssemblyB\assemblyB.deps.json --runtimeconfig pathToAssemblyB\assemblyB.runtimeconfig.json AssemblyA.dll
и затем вы можете загрузить сборку, как описано в других ответах, с помощью
var myAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath("pathToAssemblyB\\AssemblyB.dll");
Таким образом, он корректно разрешит все зависимости для AssemblyB, но не для AssemblyA. Это обратная проблема, но если у вас есть небольшое приложение, которое хочет сделать удаленное взаимодействие в большом приложении, это полезно. Другая проблема заключается в том, что вам нужно знать, что вы собираетесь использовать ассемблер B при запуске приложения и что оно работает только один раз за выполнение. Таким образом, существует другой набор проблем, и вы можете выбрать свой подход в зависимости от вашей ситуации. Обратите внимание, что это неподдерживаемая/недокументированная функция, но она используется в основных инструментальных средствах EF, поэтому сейчас она "жизнеспособна"...
Ответ 7
Я думаю, что это ниже будет работать для вас, и надеюсь, что это может помочь некоторым новичкам MEF2, как я.
/// <summary>
/// Gets the assemblies that belong to the application .exe subfolder.
/// </summary>
/// <returns>A list of assemblies.</returns>
private static IEnumerable<Assembly> GetAssemblies()
{
string executableLocation = AppContext.BaseDirectory;
string directoryToSearch = Path.Combine(Path.GetDirectoryName(executableLocation), "Plugins");
foreach (string file in Directory.EnumerateFiles(directoryToSearch, "*.dll"))
{
Assembly assembly = null;
try
{
//Load assembly using byte array
byte[] rawAssembly = File.ReadAllBytes(file);
assembly = Assembly.Load(rawAssembly);
}
catch (Exception)
{
}
if (assembly != null)
{
yield return assembly;
}
}
}
еще один, но в .netstandard1.3 ни один не был доступен.
var assembiles = Directory.GetFiles(Assembly.GetEntryAssembly().Location, "*.dll", SearchOption.TopDirectoryOnly)
.Select(AssemblyLoadContext.Default.LoadFromAssemblyPath);