Eklentilerle .NET Core uygulaması oluşturma
Bu öğreticide eklentileri yüklemek için özel AssemblyLoadContext oluşturma işlemleri gösterilmektedir. AssemblyDependencyResolver eklentinin bağımlılıklarını çözümlemek için kullanılır. Öğretici, eklentinin bağımlılıklarını barındırma uygulamasından doğru şekilde yalıtıyor. Şunları öğrenirsiniz:
- Bir projeyi eklentileri destekleyecek şekilde yapılandırma.
- Her eklentiyi yüklemek için bir özel AssemblyLoadContext oluşturun.
- Eklentilerin System.Runtime.Loader.AssemblyDependencyResolver bağımlılıklara sahip olmasını sağlamak için türünü kullanın.
- Yalnızca derleme yapıtlarını kopyalayarak kolayca dağıtabileceğiniz eklentiler yazın.
Önkoşullar
- .NET 5 SDK'sını veya daha yeni bir sürümü yükleyin.
Not
Örnek kod .NET 5'i hedefler, ancak kullandığı tüm özellikler .NET Core 3.0'da kullanıma sunulmuştur ve o zamandan beri tüm .NET sürümlerinde kullanılabilir.
Uygulama oluşturma
İlk adım uygulamayı oluşturmaktır:
Yeni bir klasör oluşturun ve bu klasörde aşağıdaki komutu çalıştırın:
dotnet new console -o AppWithPlugin
Projeyi oluşturmayı kolaylaştırmak için aynı klasörde bir Visual Studio çözüm dosyası oluşturun. Şu komutu çalıştırın:
dotnet new sln
Uygulama projesini çözüme eklemek için aşağıdaki komutu çalıştırın:
dotnet sln add AppWithPlugin/AppWithPlugin.csproj
Artık uygulamamızın iskeletini doldurabiliriz. AppWithPlugin/Program.cs dosyasındaki kodu aşağıdaki kodla değiştirin:
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);
}
}
}
}
Eklenti arabirimlerini oluşturma
Eklentilerle uygulama oluşturmanın bir sonraki adımı, eklentilerin uygulaması gereken arabirimi tanımlamaktır. Uygulamanız ve eklentileriniz arasında iletişim kurmak için kullanmayı planladığınız türleri içeren bir sınıf kitaplığı oluşturmanızı öneririz. Bu bölüm, tam uygulamanızı göndermenize gerek kalmadan eklenti arabiriminizi bir paket olarak yayımlamanıza olanak tanır.
Projenin kök klasöründe komutunu çalıştırın dotnet new classlib -o PluginBase
. Ayrıca, projeyi çözüm dosyasına eklemek için komutunu çalıştırın dotnet sln add PluginBase/PluginBase.csproj
. PluginBase/Class1.cs
Dosyayı silin ve adlı klasörde ICommand.cs
aşağıdaki arabirim tanımıyla yeni bir dosya PluginBase
oluşturun:
namespace PluginBase
{
public interface ICommand
{
string Name { get; }
string Description { get; }
int Execute();
}
}
Bu ICommand
arabirim, tüm eklentilerin uygulayacağı arabirimdir.
Artık arabirim tanımlandığına ICommand
göre, uygulama projesi biraz daha doldurulabilir. Kök klasörden AppWithPlugin
komutuyla dotnet add AppWithPlugin/AppWithPlugin.csproj reference PluginBase/PluginBase.csproj
projeden PluginBase
projeye bir başvuru ekleyin.
// Load commands from plugins
Belirli dosya yollarından eklentileri yüklemesini sağlamak için açıklamayı aşağıdaki kod parçacığıyla değiştirin:
string[] pluginPaths = new string[]
{
// Paths to plugins to load.
};
IEnumerable<ICommand> commands = pluginPaths.SelectMany(pluginPath =>
{
Assembly pluginAssembly = LoadPlugin(pluginPath);
return CreateCommands(pluginAssembly);
}).ToList();
Ardından açıklamasını // Output the loaded commands
aşağıdaki kod parçacığıyla değiştirin:
foreach (ICommand command in commands)
{
Console.WriteLine($"{command.Name}\t - {command.Description}");
}
// Execute the command with the name passed as an argument
Açıklamasını aşağıdaki kod parçacığıyla değiştirin:
ICommand command = commands.FirstOrDefault(c => c.Name == commandName);
if (command == null)
{
Console.WriteLine("No such command is known.");
return;
}
command.Execute();
Son olarak, burada gösterildiği gibi ve CreateCommands
adlı LoadPlugin
sınıfına Program
statik yöntemler ekleyin:
static Assembly LoadPlugin(string relativePath)
{
throw new NotImplementedException();
}
static IEnumerable<ICommand> CreateCommands(Assembly assembly)
{
int count = 0;
foreach (Type type in assembly.GetTypes())
{
if (typeof(ICommand).IsAssignableFrom(type))
{
ICommand result = Activator.CreateInstance(type) as ICommand;
if (result != null)
{
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}");
}
}
Eklentileri yükleme
Artık uygulama, yüklenen eklenti derlemelerinden komutları doğru bir şekilde yükleyip örnekleyebilir, ancak eklenti derlemelerini yükleyemiyor. AppWithPlugin klasöründe aşağıdaki içeriklere sahip PluginLoadContext.cs adlı bir dosya oluşturun:
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;
}
}
}
türü PluginLoadContext
türünden AssemblyLoadContexttüretilir. Tür AssemblyLoadContext
, geliştiricilerin derleme sürümlerinin çakışmaması için yüklenen derlemeleri farklı gruplar halinde yalıtmasına olanak tanıyan çalışma zamanındaki özel bir türüdür. Ayrıca bir özel AssemblyLoadContext
, derlemeleri yüklemek ve varsayılan davranışı geçersiz kılmak için farklı yollar seçebilir. , PluginLoadContext
derleme adlarını yollara çözümlemek için .NET Core 3.0'da tanıtılan türün bir örneğini AssemblyDependencyResolver
kullanır. AssemblyDependencyResolver
nesnesi bir .NET sınıf kitaplığının yolu ile oluşturulur. Derlemeleri ve yerel kitaplıkları, yolu oluşturucuya geçirilen sınıf kitaplığının .deps.json dosyasına göre göreli yollarına AssemblyDependencyResolver
çözümler. Özel AssemblyLoadContext
, eklentilerin kendi bağımlılıklarına sahip olmasını sağlar ve AssemblyDependencyResolver
bağımlılıkları doğru şekilde yüklemeyi kolaylaştırır.
Artık proje türüne AppWithPlugin
PluginLoadContext
sahip olduğuna göre yöntemini aşağıdaki gövdeyle güncelleştirin Program.LoadPlugin
:
static Assembly LoadPlugin(string relativePath)
{
// Navigate up to the solution root
string root = Path.GetFullPath(Path.Combine(
Path.GetDirectoryName(
Path.GetDirectoryName(
Path.GetDirectoryName(
Path.GetDirectoryName(
Path.GetDirectoryName(typeof(Program).Assembly.Location)))))));
string pluginLocation = Path.GetFullPath(Path.Combine(root, relativePath.Replace('\\', Path.DirectorySeparatorChar)));
Console.WriteLine($"Loading commands from: {pluginLocation}");
PluginLoadContext loadContext = new PluginLoadContext(pluginLocation);
return loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(pluginLocation)));
}
Her eklenti için farklı PluginLoadContext
bir örnek kullanıldığında, eklentiler sorun olmadan farklı ve hatta çakışan bağımlılıklara sahip olabilir.
Bağımlılıkları olmayan basit eklenti
Kök klasöre geri döndüğünüzde aşağıdakileri yapın:
adlı
HelloPlugin
yeni bir sınıf kitaplığı projesi oluşturmak için aşağıdaki komutu çalıştırın:dotnet new classlib -o HelloPlugin
Projeyi
AppWithPlugin
çözüme eklemek için aşağıdaki komutu çalıştırın:dotnet sln add HelloPlugin/HelloPlugin.csproj
HelloPlugin/Class1.cs dosyasını HelloCommand.cs adlı bir dosyayla aşağıdaki içerikle değiştirin:
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;
}
}
}
Şimdi HelloPlugin.csproj dosyasını açın. Şunun gibi görünmelidir:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>
Etiketlerin <PropertyGroup>
arasına aşağıdaki öğeyi ekleyin:
<EnableDynamicLoading>true</EnableDynamicLoading>
, <EnableDynamicLoading>true</EnableDynamicLoading>
eklenti olarak kullanılabilmesi için projeyi hazırlar. Diğer şeylerin yanında, bu işlem tüm bağımlılıklarını projenin çıkışına kopyalar. Diğer ayrıntılar için bkz: EnableDynamicLoading
.
Etiketlerin <Project>
arasına aşağıdaki öğeleri ekleyin:
<ItemGroup>
<ProjectReference Include="..\PluginBase\PluginBase.csproj">
<Private>false</Private>
<ExcludeAssets>runtime</ExcludeAssets>
</ProjectReference>
</ItemGroup>
<Private>false</Private>
Öğe önemlidir. Bu, MSBuild'e PluginBase.dll HelloPlugin çıkış dizinine kopyalamaması gerektiğini bildirir. PluginBase.dll derlemesi çıkış dizininde varsa, PluginLoadContext
derlemeyi orada bulur ve HelloPlugin.dll derlemeyi yüklerken yükler. Bu noktada türü, HelloPlugin.HelloCommand
varsayılan yük bağlamı ICommand
içine yüklenen arabirimi değilICommand
, projenin çıkış dizinindeki HelloPlugin
PluginBase.dll arabirimini uygular. Çalışma zamanı bu iki türü farklı derlemelerden farklı türler olarak gördüğünden AppWithPlugin.Program.CreateCommands
, yöntemi komutları bulamaz. Sonuç olarak, <Private>false</Private>
eklenti arabirimlerini içeren derlemeye başvuru için meta veriler gereklidir.
Benzer şekilde, <ExcludeAssets>runtime</ExcludeAssets>
öğe diğer paketlere PluginBase
başvuruyorsa da önemlidir. Bu ayar, <Private>false</Private>
projenin veya bağımlılıklarından birinin içerebileceği paket başvuruları PluginBase
üzerinde çalışır.
Proje tamamlandıktan sonra eklentinin HelloPlugin
AppWithPlugin
nerede HelloPlugin
bulunabileceğini öğrenmek için projeyi güncelleştirmeniz gerekir. Açıklamadan // Paths to plugins to load
sonra dizinin bir öğesi pluginPaths
olarak öğesini ekleyin @"HelloPlugin\bin\Debug\net5.0\HelloPlugin.dll"
(bu yol kullandığınız .NET Core sürümüne göre farklı olabilir).
Kitaplık bağımlılıkları içeren eklenti
Neredeyse tüm eklentiler basit bir "Merhaba Dünya" değerinden daha karmaşıktır ve birçok eklentinin diğer kitaplıklara bağımlılıkları vardır. JsonPlugin
Örnekteki ve OldJsonPlugin
projeleri, üzerinde Newtonsoft.Json
NuGet paket bağımlılıklarına sahip iki eklenti örneği gösterir. Bu nedenle, tüm eklenti projelerinin tüm bağımlılıklarını çıkışına dotnet build
kopyalaması için proje özelliklerine eklemesi <EnableDynamicLoading>true</EnableDynamicLoading>
gerekir. ile dotnet publish
sınıf kitaplığını yayımlamak da tüm bağımlılıklarını yayımlama çıkışına kopyalar.
Örnekteki diğer örnekler
Bu öğreticinin tam kaynak kodu dotnet/samples deposunda bulunabilir. Tamamlanan örnek birkaç davranış örneği AssemblyDependencyResolver
daha içerir. Örneğin, AssemblyDependencyResolver
nesnesi yerel kitaplıkların yanı sıra NuGet paketlerine dahil edilen yerelleştirilmiş uydu derlemelerini de çözümleyebilir. UVPlugin
örnek deposundaki ve FrenchPlugin
bu senaryoları gösterir.
NuGet paketinden eklenti arabirimine başvurma
Adlı A.PluginBase
NuGet paketinde tanımlanmış eklenti arabirimine sahip bir uygulama A olduğunu varsayalım. Eklenti projenizde pakete nasıl doğru başvurursunuz? Proje başvuruları için, proje dosyasındaki ProjectReference
öğesinde meta verilerin kullanılması <Private>false</Private>
dll dosyasının çıkışa kopyalanmasını engelledi.
Pakete doğru şekilde başvurmak A.PluginBase
için proje dosyasındaki <PackageReference>
öğesini aşağıdakiyle değiştirmek istiyorsunuz:
<PackageReference Include="A.PluginBase" Version="1.0.0">
<ExcludeAssets>runtime</ExcludeAssets>
</PackageReference>
Bu, derlemelerin A.PluginBase
eklentinizin çıkış dizinine kopyalanmasını önler ve eklentinizin A'nın sürümünü A.PluginBase
kullanmasını sağlar.
Eklenti hedef çerçevesi önerileri
Eklenti bağımlılığı yüklemesi .deps.json dosyasını kullandığından, eklentinin hedef çerçevesiyle ilgili bir gotcha vardır. Özellikle, eklentileriniz .NET Standard sürümü yerine .NET 5 gibi bir çalışma zamanını hedeflemelidir. .deps.json dosyası, projenin hedeflediği çerçeveye göre oluşturulur ve birçok .NET Standard uyumlu paket, .NET Standard'a ve belirli çalışma zamanları için uygulama derlemelerine yönelik başvuru derlemeleri sevk ettiğinden, .deps.json uygulama derlemelerini doğru göremeyebilir veya beklediğiniz .NET Core sürümü yerine bir derlemenin .NET Standard sürümünü alabilir.
Eklenti çerçevesi başvuruları
Eklentiler şu anda sürece yeni çerçeveler ekleyemiyor. Örneğin, çerçeveyi kullanan bir eklentiyi yalnızca kök Microsoft.NETCore.App
çerçeveyi Microsoft.AspNetCore.App
kullanan bir uygulamaya yükleyemezsiniz. Konak uygulamasının eklentiler tarafından gereken tüm çerçevelere başvuru bildirmesi gerekir.