Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Ez az oktatóanyag bemutatja, hogyan hozhat létre egyéni AssemblyLoadContext beépülő modulokat. Az An AssemblyDependencyResolver a beépülő modul függőségeinek feloldására szolgál. Az oktatóanyag külön szerelvénykörnyezetet biztosít a beépülő modul függőségeihez, lehetővé téve a beépülő modulok és az üzemeltetési alkalmazás különböző szerelvényfüggőségeit. Megtudhatja, hogyan:
- Projekt strukturálása beépülő modulok támogatásához.
- Hozzon létre egy egyedi AssemblyLoadContext az egyes beépülő modulok betöltéséhez.
- A System.Runtime.Loader.AssemblyDependencyResolver típust használja a beépülő modulok függőségeinek megengedésére.
- Olyan beépülő modulok készítése, amelyek egyszerűen üzembe helyezhetők a build-artifaktumok másolásával.
Megjegyzés
A nem megbízható kód nem tölthető be biztonságosan egy megbízható .NET-folyamatba. A biztonság vagy a megbízhatóság határának biztosításához fontolja meg az operációs rendszer vagy a virtualizálási platform által biztosított technológiát.
Előfeltételek
- A legújabb .NET SDK
- Visual Studio Code szerkesztő
- A C# fejlesztőkészlet
Az alkalmazás létrehozása
Az első lépés az alkalmazás létrehozása:
Hozzon létre egy új mappát, és ebben a mappában futtassa a következő parancsot:
dotnet new console -o AppWithPluginA projekt létrehozásának megkönnyítése érdekében hozzon létre egy Visual Studio-megoldásfájlt ugyanabban a mappában. Futtassa a következő parancsot:
dotnet new slnFuttassa a következő parancsot az alkalmazásprojekt megoldáshoz való hozzáadásához:
dotnet sln add AppWithPlugin/AppWithPlugin.csproj
Most már kitölthetjük az alkalmazás csontvázát. Cserélje le a kódot az AppWithPlugin/Program.cs fájlban a következő kódra:
using PluginBase;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
namespace AppWithPlugin
{
class Program
{
static void Main(string[] args)
{
try
{
if (args.Length == 1 && args[0] == "/d")
{
Console.WriteLine("Waiting for any key...");
Console.ReadLine();
}
// Load commands from plugins.
if (args.Length == 0)
{
Console.WriteLine("Commands: ");
// Output the loaded commands.
}
else
{
foreach (string commandName in args)
{
Console.WriteLine($"-- {commandName} --");
// Execute the command with the name passed as an argument.
Console.WriteLine();
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
}
A beépülő modul felületeinek létrehozása
Az alkalmazások beépülő modulokkal való létrehozásának következő lépése annak a felületnek a meghatározása, amelyet a beépülő moduloknak implementálniuk kell. Javasoljuk, hogy hozzon létre egy osztálytárat, amely tartalmazza az alkalmazás és a beépülő modulok közötti kommunikációhoz használni kívánt típusokat. Ez a részleg lehetővé teszi, hogy a beépülő modul felületét csomagként tegye közzé anélkül, hogy a teljes alkalmazást be kellene szállítania.
A projekt gyökérmappájában futtassa a következőt dotnet new classlib -o PluginBase: . Futtassa a dotnet sln add PluginBase/PluginBase.csproj parancsot is a projekt megoldásfájlhoz való hozzáadásához. Törölje a PluginBase/Class1.cs fájlt, és hozzon létre egy új fájlt a PluginBase következő felületdefinícióval elnevezett ICommand.cs mappában:
namespace PluginBase
{
public interface ICommand
{
string Name { get; }
string Description { get; }
int Execute();
}
}
Ez a ICommand interfész az, amelyet az összes beépülő modul megvalósít.
Most, hogy a ICommand felület definiálva van, az alkalmazásprojektet egy kicsit jobban ki lehet tölteni. Adjon hozzá egy hivatkozást a AppWithPlugin projektből a PluginBase projekthez a dotnet add AppWithPlugin/AppWithPlugin.csproj reference PluginBase/PluginBase.csproj gyökérmappából származó paranccsal.
Cserélje le a // Load commands from plugins megjegyzést a következő kódrészletre, hogy lehetővé tegye a beépülő modulok betöltését a megadott fájlelérési utakról:
string[] pluginPaths = new string[]
{
// Paths to plugins to load.
};
IEnumerable<ICommand> commands = pluginPaths.SelectMany(pluginPath =>
{
Assembly pluginAssembly = LoadPlugin(pluginPath);
return CreateCommands(pluginAssembly);
}).ToList();
Ezután cserélje le a // Output the loaded commands megjegyzést a következő kódrészletre:
foreach (ICommand command in commands)
{
Console.WriteLine($"{command.Name}\t - {command.Description}");
}
Cserélje le a // Execute the command with the name passed as an argument megjegyzést a következő kódrészletre:
ICommand command = commands.FirstOrDefault(c => c.Name == commandName);
if (command == null)
{
Console.WriteLine("No such command is known.");
return;
}
command.Execute();
Végül adjon hozzá statikus metódusokat a Program nevesített LoadPlugin osztályhoz, és CreateCommandsaz itt látható módon:
static Assembly LoadPlugin(string relativePath)
{
throw new NotImplementedException();
}
static IEnumerable<ICommand> CreateCommands(Assembly assembly)
{
int count = 0;
foreach (var type in assembly.GetTypes().Where(t => typeof(ICommand).IsAssignableFrom(t)))
{
if (Activator.CreateInstance(type) is ICommand result)
{
count++;
yield return result;
}
}
if (count == 0)
{
string availableTypes = string.Join(",", assembly.GetTypes().Select(t => t.FullName));
throw new ApplicationException(
$"Can't find any type which implements ICommand in {assembly} from {assembly.Location}.\n" +
$"Available types: {availableTypes}");
}
}
Beépülő modulok betöltése
Az alkalmazás most már képes megfelelően betölteni és példányosítani a parancsokat a betöltött beépülő modul-szerelvényekből, de továbbra sem tudja betölteni a beépülő modul szerelvényeket. Hozzon létre egy PluginLoadContext.cs nevű fájlt az AppWithPlugin mappában a következő tartalommal:
using System;
using System.Reflection;
using System.Runtime.Loader;
namespace AppWithPlugin
{
class PluginLoadContext : AssemblyLoadContext
{
private AssemblyDependencyResolver _resolver;
public PluginLoadContext(string pluginPath)
{
_resolver = new AssemblyDependencyResolver(pluginPath);
}
protected override Assembly Load(AssemblyName assemblyName)
{
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
return LoadFromAssemblyPath(assemblyPath);
}
return null;
}
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (libraryPath != null)
{
return LoadUnmanagedDllFromPath(libraryPath);
}
return IntPtr.Zero;
}
}
}
A PluginLoadContext típus a következőből AssemblyLoadContextszármazik: . A AssemblyLoadContext típus egy speciális típus a futtatókörnyezetben, amely lehetővé teszi a fejlesztők számára, hogy a betöltött szerelvényeket különböző csoportokba különítsék el, hogy a szerelvényverziók ne ütközhessenek. Továbbá, egy egyedi AssemblyLoadContext különböző útvonalakat választhat a szerelvények betöltéséhez, és felülírhatja az alapértelmezett viselkedést. A PluginLoadContext a AssemblyDependencyResolver típus egy példányát használja, amely a .NET Core 3.0-ban lett bevezetve, a szerelvénynevek elérési utakra való feloldására. Az AssemblyDependencyResolver objektum egy .NET-osztálytár elérési útjával jön létre. Feloldja a szerelvényeket és a natív kódtárakat a relatív elérési utakra annak az osztálytárnak a .deps.json fájlja alapján, amelynek elérési útját a konstruktornak AssemblyDependencyResolver adták át. Az egyéni AssemblyLoadContext lehetővé teszi, hogy a beépülő modulok saját függőségekkel rendelkezzenek, és AssemblyDependencyResolver megkönnyíti a függőségek helyes betöltését.
Most, hogy a AppWithPlugin projekt rendelkezik a PluginLoadContext típussal, frissítse a metódust Program.LoadPlugin a következő törzstel:
static Assembly LoadPlugin(string relativePath)
{
// Navigate up to the solution root
string root = Path.GetFullPath(
Path.Combine(typeof(Program).Assembly.Location, "..", "..", "..", "..", ".."));
string pluginLocation = Path.GetFullPath(Path.Combine(root, relativePath.Replace('\\', Path.DirectorySeparatorChar)));
Console.WriteLine($"Loading commands from: {pluginLocation}");
PluginLoadContext loadContext = new(pluginLocation);
return loadContext.LoadFromAssemblyName(new(Path.GetFileNameWithoutExtension(pluginLocation)));
}
Ha minden beépülő modulhoz más-más PluginLoadContext példányt használ, a beépülő modulok különböző vagy akár ütköző függőségekkel is rendelkezhetnek probléma nélkül.
Egyszerű beépülő modul függőségek nélkül
A gyökérmappába visszatérve tegye a következőket:
Futtassa a következő parancsot egy új osztálytárprojekt
HelloPluginlétrehozásához:dotnet new classlib -o HelloPluginFuttassa a következő parancsot a projekt hozzáadásához a
AppWithPluginmegoldáshoz:dotnet sln add HelloPlugin/HelloPlugin.csprojCserélje le a HelloPlugin/Class1.cs fájlt egy HelloCommand.cs nevű fájlra a következő tartalommal:
using PluginBase;
using System;
namespace HelloPlugin
{
public class HelloCommand : ICommand
{
public string Name { get => "hello"; }
public string Description { get => "Displays hello message."; }
public int Execute()
{
Console.WriteLine("Hello !!!");
return 0;
}
}
}
Most nyissa meg a HelloPlugin.csproj fájlt. A következőhöz hasonlóan kell kinéznie:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>
A <PropertyGroup> címkék között adja hozzá a következő elemet:
<EnableDynamicLoading>true</EnableDynamicLoading>
A <EnableDynamicLoading>true</EnableDynamicLoading> előkészíti a projektet, hogy beépülő modulként használható legyen. Ez többek között az összes függőségét a projekt kimenetére másolja. További információ: EnableDynamicLoading.
Adja hozzá a következő elemeket a <Project> címkék között:
<ItemGroup>
<ProjectReference Include="..\PluginBase\PluginBase.csproj">
<Private>false</Private>
<ExcludeAssets>runtime</ExcludeAssets>
</ProjectReference>
</ItemGroup>
Az <Private>false</Private> elem fontos. Ez arra utasítja az MSBuildet, hogy ne másolja aPluginBase.dll a HelloPlugin kimeneti könyvtárába. Ha a PluginBase.dll összeállítás megtalálható a kimeneti könyvtárban, PluginLoadContext betölti, amikor betölti a HelloPlugin.dll összeállítást. Ezen a ponton a HelloPlugin.HelloCommand típus a PluginBase.dll illesztőfelületet fogja implementálni a HelloPlugin projekt kimeneti könyvtárában, nem pedig az ICommand felületet, amely az alapértelmezett terhelési környezetbe van betöltve. Mivel a futtatókörnyezet ezt a két típust különböző szerelvénytípusokként látja, a AppWithPlugin.Program.CreateCommands metódus nem találja a parancsokat. Ennek eredményeképpen a <Private>false</Private> metaadatok szükségesek a beépülő modul felületeit tartalmazó szerelvényre való hivatkozáshoz.
Hasonlóképpen, az <ExcludeAssets>runtime</ExcludeAssets> elem akkor is fontos, ha más PluginBase csomagokra hivatkozik. Ez a beállítás ugyanolyan hatással van, mint <Private>false</Private>, de a projekt vagy annak valamely függősége által tartalmazott csomaghivatkozások esetén működik PluginBase.
Most, hogy a HelloPlugin projekt befejeződött, frissítenie kell a AppWithPlugin projektet, hogy megtudja, hol található a HelloPlugin beépülő modul. A // Paths to plugins to load megjegyzés után adja hozzá @"HelloPlugin\bin\Debug\net5.0\HelloPlugin.dll" (ez az elérési út a használt .NET Core-verziótól függően eltérő lehet) a pluginPaths tömb elemeként.
Beépülő modul könyvtárfüggőségekkel
Szinte minden beépülő modul összetettebb, mint egy egyszerű "Hello World", és sok beépülő modul függőségekkel rendelkezik más kódtáraktól. A mintában szereplő JsonPlugin és OldJsonPlugin projektek két példát mutatnak be a Newtonsoft.Json-en keresztül NuGet-csomag függőségekkel rendelkező beépülő modulokra. Emiatt minden beépülő modulprojektnek hozzá kell adnia <EnableDynamicLoading>true</EnableDynamicLoading>-t a projekttulajdonságokhoz, hogy az összes függőséget dotnet build kimenetére másolja. Az osztálytár dotnet publish közzététele az összes függőségét is a közzétételi kimenetbe másolja.
További példák a mintában
Az oktatóanyag teljes forráskódja megtalálható a dotnet/samples adattárban. A kész minta néhány más viselkedési AssemblyDependencyResolver példát is tartalmaz. Az objektum például képes feloldani a AssemblyDependencyResolver natív kódtárakat és a NuGet-csomagokban található honosított műholdas szerelvényeket is. A UVPlugin és a FrenchPlugin mintatárban ezek a forgatókönyvek kerülnek bemutatásra.
Beépülő modul felületére hivatkozik egy NuGet-csomagból
Tegyük fel, hogy van egy A alkalmazás, amely a NuGet-csomagban definiált beépülő modul felülettel rendelkezik A.PluginBase. Hogyan hivatkozhat helyesen a csomagra a beépülő modulprojektben? Projekthivatkozások esetén a <Private>false</Private> projektfájlban lévő ProjectReference elem metaadatainak használata megakadályozta a dll kimenetbe másolását.
Ahhoz, hogy helyesen hivatkozzon a A.PluginBase csomagra, módosítania kell a <PackageReference> elemet a projekt fájlban a következőre:
<PackageReference Include="A.PluginBase" Version="1.0.0">
<ExcludeAssets>runtime</ExcludeAssets>
</PackageReference>
Ez megakadályozza a A.PluginBase szerelvények másolását a beépülő modul kimeneti könyvtárába, és biztosítja, hogy a beépülő modul az A A.PluginBase változatát használja.
Beépülő modul cél-keretrendszerének ajánlásai
Mivel a beépülő modul függőségeinek betöltése a .deps.json fájlt használja, van egy buktató a beépülő modul célkeretrendszeréhez kapcsolódóan. A beépülő moduloknak a .NET Standard verziója helyett egy futtatókörnyezetet, például a .NET 5-öt kell céloznia. A .deps.json fájl a projekt céljainak keretrendszere alapján jön létre, és mivel számos .NET Standard-kompatibilis csomag referenciális szerelvényeket szállít a .NET Standard és az implementációs szerelvények számára adott futtatókörnyezetekhez, előfordulhat, hogy a .deps.json nem látja megfelelően a megvalósítási szerelvényeket, vagy a várt .NET Core-verzió helyett egy szerelvény .NET Standard verzióját fogja meg.
A beépülő modul keretrendszer hivatkozásai
A beépülő modulok jelenleg nem tudnak új keretrendszereket bevezetni a folyamatba. Nem tölthető be például olyan beépülő modul, amely a Microsoft.AspNetCore.App keretrendszert használja olyan alkalmazásba, amely csak a gyökér Microsoft.NETCore.App keretrendszert használja. A gazdaalkalmazásnak deklarálnia kell a beépülő modulok által igényelt összes keretrendszerre mutató hivatkozásokat.