Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Bu öğreticide eklentileri yüklemek için özel bir AssemblyLoadContext nasıl oluşturulacağı gösterilmektedir. Eklentinin bağımlılıklarını çözmek için bir AssemblyDependencyResolver kullanılır. Öğretici, eklenti bağımlılıkları için ayrı bir derleme bağlamı sağlayarak eklentilerle barındırma uygulaması arasında farklı derleme bağımlılıklarına olanak sağlar. Nasıl yapılacağını öğreneceksiniz:
- Bir projeyi eklentileri destekleyecek şekilde yapılandırma.
- Her eklentiyi yüklemek için özel bir AssemblyLoadContext oluşturun.
- Eklentilerin bağımlılıklara sahip olmasını sağlamak için System.Runtime.Loader.AssemblyDependencyResolver türünü kullanın.
- Sadece derleme çıktıları kopyalanarak kolayca dağıtılabilen eklentiler geliştirin.
Uyarı
Güvenilmeyen kod güvenilir bir .NET işlemine güvenli bir şekilde yüklenemez. Güvenlik veya güvenilirlik sınırı sağlamak için işletim sisteminiz veya sanallaştırma platformunuz tarafından sağlanan bir teknolojiyi göz önünde bulundurun.
Önkoşullar
- En son .NET SDK
- Visual Studio Code düzenleyici
- C# Geliştirme Kiti
Uygulamayı 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. Aşağıdaki 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ı için 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öndermek zorunda kalmadan eklenti arabiriminizi bir paket olarak yayımlamanıza olanak tanır.
Projenin kök klasöründe dotnet new classlib -o PluginBase
çalıştırın. Ayrıca, projeyi çözüm dosyasına eklemek için dotnet sln add PluginBase/PluginBase.csproj
çalıştırın.
PluginBase/Class1.cs
dosyasını silin ve aşağıdaki arabirim tanımıyla ICommand.cs
adlı PluginBase
klasöründe yeni bir dosya oluşturun:
namespace PluginBase
{
public interface ICommand
{
string Name { get; }
string Description { get; }
int Execute();
}
}
Bu ICommand
arabirimi, tüm eklentilerin uygulayacağı arabirimdir.
artık ICommand
arabirimi tanımlandığına göre, uygulama projesi biraz daha doldurulabilir. Kök klasörden dotnet add AppWithPlugin/AppWithPlugin.csproj reference PluginBase/PluginBase.csproj
komutuyla AppWithPlugin
projesinden PluginBase
projesine bir referans ekleyin.
// Load commands from plugins
açıklamasını aşağıdaki kod parçacığıyla değiştirerek belirli dosya yollarından eklentileri yüklemesini sağlayın:
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 // Output the loaded commands
açıklamasını 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 LoadPlugin
ve CreateCommands
adlı Program
sınıfına 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ükleyebilir ve başlatabilir, 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;
}
}
}
PluginLoadContext
türü AssemblyLoadContext'den türetilmiştir.
AssemblyLoadContext
türü, geliştiricilerin derleme sürümlerinin çakışmamasını sağlamak için yüklenen derlemeleri farklı gruplar halinde yalıtmasına olanak tanıyan çalışma zamanındaki özel bir türdür. Ayrıca, özel bir AssemblyLoadContext
derlemeleri yüklemek ve varsayılan davranışı geçersiz kılmak için farklı yollar seçebilir.
PluginLoadContext
, derleme adlarını yollara dönüştürmek için .NET Core 3.0'da tanıtılmış olan AssemblyDependencyResolver
türünün bir örneğini kullanıyor.
AssemblyDependencyResolver
nesnesi bir .NET sınıf kitaplığının yolu kullanılarak oluşturulur. Derlemeleri ve yerel kitaplıkları, yolu AssemblyDependencyResolver
oluşturucuya geçirilen sınıf kitaplığının .deps.json dosyasına göre göreli yollarına çö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.
AppWithPlugin
projesi PluginLoadContext
türüne sahip olduğuna göre, Program.LoadPlugin
yöntemini aşağıdaki gövdeyle güncelleştirin:
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ı bir PluginLoadContext
örneği kullanarak, eklentiler sorun olmadan farklı veya çakışan bağımlılıklara sahip olabilir.
Bağımlılıkları olmayan basit eklenti
Kök klasöre geri dönün, aşağıdakileri yapın:
HelloPlugin
adlı 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ümüne eklemek için aşağıdaki komutu çalıştırın:dotnet sln add HelloPlugin/HelloPlugin.csproj
HelloPlugin/Class1.cs dosyasını aşağıdaki içeriklerle HelloCommand.cs adlı bir dosyayla 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. Aşağıdakine benzer görünmelidir:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>
<PropertyGroup>
etiketlerinin 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
.
<Project>
etiketlerinin arasına aşağıdaki öğeleri ekleyin:
<ItemGroup>
<ProjectReference Include="..\PluginBase\PluginBase.csproj">
<Private>false</Private>
<ExcludeAssets>runtime</ExcludeAssets>
</ProjectReference>
</ItemGroup>
<Private>false</Private>
öğesi önemlidir. Bu, MSBuild'e HelloPlugin için çıkış dizinine PluginBase.dll kopyalamaması gerektiğini bildirir.
PluginBase.dll derlemesi çıkış dizininde varsa, PluginLoadContext
derlemeyi orada bulur ve HelloPlugin.dll derlemesini yüklediğinde yükler. Bu noktada, HelloPlugin.HelloCommand
türü varsayılan yük bağlamı içine yüklenen ICommand
arabirimini değil, HelloPlugin
projesinin çıkış dizinindeki PluginBase.dllICommand
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, eklenti arabirimlerini içeren derlemeye başvuru için <Private>false</Private>
meta verileri gerekir.
Benzer şekilde, PluginBase
diğer paketlere başvuruyorsa <ExcludeAssets>runtime</ExcludeAssets>
öğesi de önemlidir. Bu ayar, <Private>false</Private>
ile aynı etkiye sahiptir, ancak PluginBase
projesinin veya bağımlılıklarından birinin içerebileceği paket başvuruları üzerinde çalışır.
HelloPlugin
projesi tamamlandıktan sonra, HelloPlugin
eklentinin nerede bulunabileceğini öğrenmek için AppWithPlugin
projesini güncelleştirmeniz gerekir.
// Paths to plugins to load
açıklamasının ardından pluginPaths
dizisinin öğesi olarak @"HelloPlugin\bin\Debug\net5.0\HelloPlugin.dll"
(bu yol kullandığınız .NET Core sürümüne göre farklı olabilir) ekleyin.
Kitaplık bağımlılıklarına sahip eklenti
Neredeyse tüm eklentiler basit bir "Merhaba Dünya" yerine daha karmaşıktır ve birçok eklentinin diğer kitaplıklara bağımlılıkları vardır. Örnekteki JsonPlugin
ve OldJsonPlugin
projeleri, Newtonsoft.Json
üzerinde 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ı dotnet build
çıkışına kopyalaması için proje özelliklerine <EnableDynamicLoading>true</EnableDynamicLoading>
eklemesi gerekir. Sınıf kitaplığını dotnet publish
ile yayımlamak, tüm bağımlılıklarıyla birlikte yayımlama çıktısına kopyalanmasını sağlar.
Örnekteki diğer örnekler
Bu öğreticinin kaynak kodunun tamamı dotnet/samples deposundabulunabilir. Tamamlanan örnek, AssemblyDependencyResolver
davranışına ilişkin birkaç örnek 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. Örnek deposundaki UVPlugin
ve FrenchPlugin
bu senaryoları gösterir.
NuGet paketinden eklenti arabirimine başvurma
NuGet paketinde A.PluginBase
adlı eklenti arabirimine sahip bir uygulama olduğunu varsayalım. Eklenti projenizde pakete nasıl doğru başvuruda bulunursunuz? Proje başvuruları için, proje dosyasındaki ProjectReference
öğesinde <Private>false</Private>
meta verilerinin kullanılması dll dosyasının çıkışa kopyalanmasını engelledi.
A.PluginBase
paketine doğru şekilde başvurmak 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, A.PluginBase
derlemelerinin eklentinizin çıkış dizinine kopyalanmasını önler ve eklentinizin A'nın A.PluginBase
sürümünü kullanmasını sağlar.
Eklenti hedef çerçevesi önerileri
Eklenti bağımlılığı yükleme .deps.json dosyasını kullandığından, eklentinin hedef çerçevesiyle ilgili bir gotcha vardır. Özel olarak, 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 Standart uyumlu paket, belirli çalışma zamanları için .NET Standard ve uygulama derlemelerine karşı derlemeler göndermek için başvuru derlemelerini sevk ettiğinden, .deps.json uygulama derlemelerini doğru şekilde göremeyebilir veya beklediğiniz .NET Core sürümü yerine bir derlemenin .NET Standard sürümünü alabilir.
Eklenti çerçevesi referansları
Eklentiler şu anda sürece yeni çerçeveler ekleyemiyor. Örneğin, Microsoft.AspNetCore.App
çerçevesini kullanan bir eklentiyi yalnızca kök Microsoft.NETCore.App
çerçevesini kullanan bir uygulamaya yükleyemezsiniz. Ana uygulamanın, eklentiler için gereken tüm çerçevelere referansları bildirmesi gerekir.