Eklentilerle .NET Core uygulaması oluşturma
Bu öğreticide, yükleme eklentilerini bir özel olarak nasıl oluşturacağınız gösterilmektedir AssemblyLoadContext . , 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ır. Şunları öğrenirsiniz:
- Eklentileri desteklemek için bir proje yapısı yapın.
- AssemblyLoadContextHer bir eklentiyi yüklemek için özel oluşturun.
- System.Runtime.Loader.AssemblyDependencyResolverEklentilerin bağımlılıklara sahip olmasını sağlamak için türü kullanın.
- Yalnızca derleme yapıtları kopyalanarak kolayca dağıtılabilecek olan eklentileri yazar.
Önkoşullar
- .NET 5 SDK veya daha yeni bir sürümünü yükler.
Not
Örnek kod .NET 5 ' i hedefler, ancak kullandığı tüm özellikler .NET Core 3,0 ' de tanıtılmıştır ve bu tarihten sonra tüm .NET sürümlerinde mevcuttur.
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 AppWithPluginprojeyi daha kolay hale getirmek için aynı klasörde bir Visual Studio çözüm dosyası oluşturun. Şu komutu çalıştırın:
dotnet new slnUygulama projesini çözüme eklemek için aşağıdaki komutu çalıştırın:
dotnet sln add AppWithPlugin/AppWithPlugin.csproj
Şimdi uygulamamızın iskektlerini dolduracağız. 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 uygulanması gereken arabirimi tanımlar. Uygulamanız ve eklentiler arasında iletişim kurmak için kullanmayı planladığınız türleri içeren bir sınıf kitaplığı almanızı öneririz. Bu bölüm, tam uygulamanızı teslim etmek zorunda kalmadan eklenti arabiriminizi bir paket olarak yayımlamanıza olanak sağlar.
Projenin kök klasöründe, öğesini çalıştırın dotnet new classlib -o PluginBase . Ayrıca, dotnet sln add PluginBase/PluginBase.csproj projeyi çözüm dosyasına eklemek için öğesini çalıştırın. Dosyayı silin PluginBase/Class1.cs ve PluginBase aşağıdaki arabirim tanımıyla adlı klasörde yeni bir dosya oluşturun ICommand.cs :
namespace PluginBase
{
public interface ICommand
{
string Name { get; }
string Description { get; }
int Execute();
}
}
Bu ICommand arabirim, tüm eklentilerin uygulayamayacağı arabirimdir.
ICommandArabirim tanımlandığına göre, uygulama projesi biraz daha fazla doldurulabilir. AppWithPlugin PluginBase Kök klasördeki komutla projeye bir başvuru ekleyin dotnet add AppWithPlugin/AppWithPlugin.csproj reference PluginBase/PluginBase.csproj .
// Load commands from pluginsVerilen dosya yollarından eklentileri yüklemesini etkinleştirmek için yorumu aşağıdaki kod parçacığı ile 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();
Sonra // Output the loaded commands yorumu aşağıdaki kod parçacığı ile değiştirin:
foreach (ICommand command in commands)
{
Console.WriteLine($"{command.Name}\t - {command.Description}");
}
// Execute the command with the name passed as an argumentYorumu 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, Program LoadPlugin CreateCommands burada gösterildiği gibi, ve adlı sınıfa 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ükle
Artık uygulama yüklenen eklenti derlemelerinden komutları doğru bir şekilde yükleyebilir ve örneklendirilecek, ancak hala eklenti derlemelerini yükleyemeyebilir. Appwithplugin klasöründe aşağıdaki Içeriğe 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;
}
}
}
PluginLoadContextTür öğesinden türetilir AssemblyLoadContext . AssemblyLoadContextTür çalışma zamanında, geliştiricilerin, derleme sürümlerinin çakışmadığından emin olmak için yüklü derlemeleri farklı gruplara yalıtmalarına olanak tanıyan özel bir türdür. Ayrıca, özel, AssemblyLoadContext derlemeleri yüklemek için farklı yollar seçebilir ve varsayılan davranışı geçersiz kılabilir. , PluginLoadContext AssemblyDependencyResolver Derleme adlarını yollara çözümlemek Için .net Core 3,0 ' de tanıtılan türün bir örneğini kullanır. AssemblyDependencyResolverNesnesi, .NET sınıf kitaplığı yoluyla oluşturulur. Derlemeleri ve yerel kitaplıkları, yolu oluşturucuya geçilen sınıf kitaplığı için . Deps. JSON dosyasını temel alarak göreli yollarına çözümler AssemblyDependencyResolver . Ö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.
AppWithPluginProjenin PluginLoadContext türü olduğuna Program.LoadPlugin göre, yöntemi aşağıdaki gövdele 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)));
}
PluginLoadContextHer eklenti için farklı bir örnek kullanarak, Eklentiler, sorun olmadan farklı veya 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 şunları yapın:
Adlı yeni bir sınıf kitaplığı projesi oluşturmak için aşağıdaki komutu çalıştırın
HelloPlugin:dotnet new classlib -o HelloPluginProjeyi çözüme eklemek için aşağıdaki komutu çalıştırın
AppWithPlugin:dotnet sln add HelloPlugin/HelloPlugin.csprojMerhaba Plugin/SınıfAdı. cs dosyasını, aşağıdaki Içeriğe sahip Merhaba komut. 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. Şunun gibi görünmelidir:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5</TargetFramework>
</PropertyGroup>
</Project>
<PropertyGroup>Etiketler arasında, aşağıdaki öğeyi ekleyin:
<EnableDynamicLoading>true</EnableDynamicLoading>
<EnableDynamicLoading>true</EnableDynamicLoading>Projeyi bir eklenti olarak kullanılabilmesi için hazırlar. Diğer şeyler arasında bu, tüm bağımlılıklarını projenin çıktısına kopyalar. Daha ayrıntılı bilgi için bkz EnableDynamicLoading ..
<Project>Etiketler arasında, 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, PluginBase.dll helloplugin için çıkış dizinine kopyalamamasını MSBuild söyler. PluginBase.dll derlemesi çıkış dizininde varsa, PluginLoadContext derlemeyi bulur ve HelloPlugin.dll derlemesini yüklediğinde yükler. Bu noktada, HelloPlugin.HelloCommand tür ICommand arabirimi HelloPlugin ICommand varsayılan yükleme bağlamına yüklenen arabirimi değil, projenin çıkış dizinindekiPluginBase.dlluygular. Çalışma zamanı bu iki türü farklı derlemelerden farklı türler olarak gördüğünden, AppWithPlugin.Program.CreateCommands Yöntem komutları bulamaz. Sonuç olarak, <Private>false</Private> eklenti arabirimlerini içeren derlemeye başvuru için meta veriler gerekir.
Benzer şekilde, <ExcludeAssets>runtime</ExcludeAssets> diğer paketlere başvuruyorsa öğesi de önemlidir PluginBase . Bu ayar, ile aynı etkiye sahiptir, <Private>false</Private> ancak PluginBase Proje veya bağımlılıklarından birinin dahil olabileceği paket başvuruları üzerinde de geçerlidir.
HelloPluginProje tamamlandığına göre, AppWithPlugin eklentinin nerede bulunabileceğinizi bilmesi için projeyi güncelleştirmeniz gerekir HelloPlugin . Açıklamadan sonra // Paths to plugins to load , @"HelloPlugin\bin\Debug\netcoreapp3.0\HelloPlugin.dll" Array öğesi olarak ekleyin (Bu yol, kullandığınız .NET Core sürümüne göre farklı olabilir) pluginPaths .
Kitaplık bağımlılıkları olan eklenti
Neredeyse tüm eklentiler basit bir "Merhaba Dünya" öğesinden daha karmaşıktır ve birçok eklenti diğer kitaplıklara bağımlılıkları vardır. JsonPluginörnekteki ve OldJsonPlugin projeleri üzerinde NuGet paketi bağımlılıkları olan eklentilerin iki örneğini gösterir Newtonsoft.Json . Bu nedenle, tüm eklenti projeleri, tüm <EnableDynamicLoading>true</EnableDynamicLoading> bağımlılıklarını ' ın çıktısına kopyalayabilmeleri için proje özelliklerine eklememelidir dotnet build . Sınıf kitaplığını ile yayımlamak, dotnet publish tüm bağımlılıklarını yayımlama çıktısına da kopyalayacaktır.
Örnekteki diğer örnekler
Bu öğreticinin tüm kaynak kodu DotNet/Samples deposundabulunabilir. Tamamlanan örnek, bazı davranış örneklerini içerir AssemblyDependencyResolver . örneğin, AssemblyDependencyResolver nesne yerel kitaplıkları ve NuGet paketlerine dahil edilen yerelleştirilmiş uydu derlemelerini de çözümleyebilir. UVPlugin FrenchPlugin Örnekler deposunda ve bu senaryolar gösterilmektedir.
NuGet paketinden eklenti arabirimine başvurma
adlı bir uygulama olduğunu, NuGet paketinde tanımlanan bir eklenti arabirimine sahip olduğunu varsayalım A.PluginBase . Eklenti projenizde pakete doğru şekilde nasıl başvurdunuz? Proje başvuruları için, <Private>false</Private> ProjectReference Proje dosyasındaki öğesindeki meta verilerin kullanılması dll 'nin çıkışa kopyalanmasını engelledi.
Pakete doğru bir şekilde başvurmak için A.PluginBase <PackageReference> Proje dosyasındaki öğeyi şu şekilde değiştirmek istersiniz:
<PackageReference Include="A.PluginBase" Version="1.0.0">
<ExcludeAssets>runtime</ExcludeAssets>
</PackageReference>
Bu, A.PluginBase derlemelerin eklentinin çıkış dizinine kopyalanmasını engeller ve eklenti 'un bir sürümünü kullanmasını sağlar A.PluginBase .
Eklenti hedef Framework önerileri
Eklenti bağımlılık yüklemesi . Deps. JSON dosyasını kullandığından, eklentinin hedef çerçevesiyle ilgili bir Gotcha vardır. Özellikle, eklentilerinizin bir .NET Standard sürümü yerine .NET 5 gibi bir çalışma zamanını hedeflemesi gerekir. . Deps. JSON dosyası, projenin hedeflediği çerçeveye göre oluşturulur ve birçok .NET Standard uyumlu paket, belirli çalışma zamanları için .NET Standard ve uygulama derlemelerinin oluşturulmasına yönelik başvuru derlemeleri sunduğundan, . Deps. JSON uygulama derlemelerini doğru şekilde göremez veya istediğiniz .NET Core sürümü yerine bir derlemenin .NET Standard sürümünü alabilir.
Eklenti çerçevesi başvuruları
Şu anda, eklentiler işleme yeni çerçeveler sunmaz. Örneğin, Framework kullanan bir eklentiyi Microsoft.AspNetCore.App yalnızca kök Framework kullanan bir uygulamaya yükleyebilirsiniz Microsoft.NETCore.App . Konak uygulama, eklentiler tarafından ihtiyaç duyulan tüm çerçevelere başvuruları bildirmelidir.