使用插件创建 .NET Core 应用程序

bdac87aeb303d68fa1cace836b120179.png

使用插件创建 .NET Core 应用程序

本教程展示了如何创建自定义的  AssemblyLoadContext  来加载插件。AssemblyDependencyResolver  用于解析插件的依赖项。该教程正确地将插件依赖项与主机应用程序隔离开来。将了解如何执行以下操作:

  • 构建支持插件的项目。

  • 创建自定义  AssemblyLoadContext  加载每个插件。

  • 使用  System.Runtime.Loader.AssemblyDependencyResolver  类型允许插件具有依赖项。

  • 只需复制生成项目就可以轻松部署的作者插件。

系统必备

安装  .NET 5 SDK  或更高版本。  备注

示例代码针对 .NET 5,但它使用的所有功能都已在 .NET Core 3.0 中推出,并且在此后所有 .NET 版本中都可用。

创建应用程序

  • 第一步是创建应用程序:

创建新文件夹,并在该文件夹中运行以下命令:

.NET CLI
dotnet new console -o AppWithPlugin
  • 为了更容易生成项目,请在同一文件夹中创建一个 Visual Studio 解决方案文件。运行以下命令:

.NET CLI
dotnet new sln
  • 运行以下命令,向解决方案添加应用项目:

.NET CLI
dotnet sln add AppWithPlugin/AppWithPlugin.csproj

现在,我们可以填写应用程序的主干。使用下面的代码替换 AppWithPlugin/Program.cs 文件中的代码:

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);}}}
}

创建插件接口

使用插件生成应用的下一步是定义插件需要实现的接口。我们建议创建类库,其中包含计划用于在应用和插件之间通信的任何类型。此部分允许将插件接口作为包发布,而无需发布完整的应用程序。

在项目的根文件夹中,运行  dotnet new classlib -o PluginBase。并运行  dotnet sln add PluginBase/PluginBase.csproj  向解决方案文件添加项目。删除  PluginBase/Class1.cs  文件,并使用以下接口定义在名为  ICommand.cs  的  PluginBase  文件夹中创建新的文件:

namespace PluginBase
{public interface ICommand{string Name { get; }string Description { get; }int Execute();}
}

此  ICommand  接口是所有插件将实现的接口。

由于已定义  ICommand  接口,所以应用程序项目可以填写更多内容。使用根文件夹中的  dotnet add AppWithPlugin/AppWithPlugin.csproj reference PluginBase/PluginBase.csproj  命令将引用从  AppWithPlugin  项目添加到  PluginBase  项目。

使用以下代码片段替换  // Load commands from plugins  注释,使其能够从给定文件路径加载插件:

string[] pluginPaths = new string[]
{// Paths to plugins to load.
};IEnumerable<ICommand> commands = pluginPaths.SelectMany(pluginPath =>
{Assembly pluginAssembly = LoadPlugin(pluginPath);return CreateCommands(pluginAssembly);
}).ToList();

然后用以下代码片段替换 // Output the loaded commands 注释:

foreach (ICommand command in commands)
{Console.WriteLine($"{command.Name}\t - {command.Description}");
}

使用以下代码片段替换 // Execute the command with the name passed as an argument 注释:

ICommand command = commands.FirstOrDefault(c => c.Name == commandName);
if (command == null)
{Console.WriteLine("No such command is known.");return;
}

command.Execute(); 最后,将静态方法添加到名为  LoadPlugin  和  CreateCommands  的  Program  类,如下所示:

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}");}
}

加载插件

现在,应用程序可以正确加载和实例化来自已加载的插件程序集的命令,但仍然无法加载插件程序集。使用以下内容在 AppWithPlugin 文件夹中创建名为 PluginLoadContext.cs 的文件:

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  类型派生自  AssemblyLoadContext。AssemblyLoadContext  类型是运行时中的特殊类型,该类型允许开发人员将已加载的程序集隔离到不同的组中,以确保程序集版本不冲突。此外,自定义  AssemblyLoadContext  可以选择不同路径来加载程序集格式并重写默认行为。PluginLoadContext  使用 .NET Core 3.0 中引入的  AssemblyDependencyResolver  类型的实例将程序集名称解析为路径。AssemblyDependencyResolver  对象是使用 .NET 类库的路径构造的。它根据类库的 .deps.json 文件(其路径传递给  AssemblyDependencyResolver  构造函数)将程序集和本机库解析为它们的相对路径。自定义  AssemblyLoadContext  使插件能够拥有自己的依赖项,AssemblyDependencyResolver  使正确加载依赖项变得容易。

由于  AppWithPlugin  项目具有  PluginLoadContext  类型,所以请使用以下正文更新  Program.LoadPlugin  方法:

static Assembly LoadPlugin(string relativePath)
{// Navigate up to the solution rootstring 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)));
}

通过为每个插件使用不同的  PluginLoadContext  实例,插件可以具有不同的甚至冲突的依赖项,而不会出现问题。

不具有依赖项的简单插件

返回到根文件夹,执行以下步骤:

运行以下命令,新建一个名为  HelloPlugin  的类库项目:

.NET CLI
dotnet new classlib -o HelloPlugin

运行以下命令,将项目添加到  AppWithPlugin  解决方案中:

.NET CLI
dotnet sln add HelloPlugin/HelloPlugin.csproj

使用以下内容将 HelloPlugin/Class1.cs 文件替换为名为 HelloCommand.cs 的文件:

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;}}
}

现在,打开 HelloPlugin.csproj 文件 。它应类似于以下内容:

<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>net5</TargetFramework></PropertyGroup></Project>

在    标记之间添加以下元素:

<EnableDynamicLoading>true</EnableDynamicLoading>
<EnableDynamicLoading>true</EnableDynamicLoading>

准备项目,使其可用作插件。此外,这会将其所有依赖项复制到项目的输出中。有关更多详细信息,请参阅  EnableDynamicLoading。

在    标记之间添加以下元素:

<ItemGroup><ProjectReference Include="..\PluginBase\PluginBase.csproj"><Private>false</Private><ExcludeAssets>runtime</ExcludeAssets></ProjectReference>
</ItemGroup>

false  元素很重要。它告知 MSBuild 不要将 PluginBase.dll 复制到 HelloPlugin 的输出目录 。如果 PluginBase.dll 程序集出现在输出目录中,PluginLoadContext  将在那里查找到该程序集并在加载 HelloPlugin.dll 程序集时加载它。此时,HelloPlugin.HelloCommand  类型将从  HelloPlugin  项目的输出目录中的 PluginBase.dll 实现  ICommand  接口,而不是加载到默认加载上下文中的  ICommand  接口。因为运行时将这两种类型视为不同程序集的不同类型,所以  AppWithPlugin.Program.CreateCommands  方法找不到命令。因此,对包含插件接口的程序集的引用需要  false  元数据。

同样,如果  PluginBase  引用其他包,则  runtime  元素也很重要。此设置与  false  的效果相同,但适用于  PluginBase  项目或它的某个依赖项可能包括的包引用。

因为  HelloPlugin  项目已完成,所以应该更新  AppWithPlugin  项目,以确认可以找到  HelloPlugin  插件的位置。在  // Paths to plugins to load  注释后,添加  @"HelloPlugin\bin\Debug\netcoreapp3.0\HelloPlugin.dll"(根据所使用的 .NET Core 版本,此路径可能有所不同)作为  pluginPaths  数组的元素。

具有库依赖项的插件

几乎所有插件都比简单的“Hello World”更复杂,而且许多插件都具有其他库上的依赖项。示例中的  JsonPlugin  和  OldJsonPlugin  项目显示了具有  Newtonsoft.Json  上的 NuGet 包依赖项的两个插件示例。因此,所有插件项目都应将  true  添加到项目属性,以便它们将其所有依赖项复制到  dotnet build  的输出中。使用  dotnet publish  发布类库也会将其所有依赖项复制到发布输出。

从 NuGet 包引用插件接口

假设存在应用 A,它具有 NuGet 包(名为  A.PluginBase)中定义的插件接口。如何在插件项目中正确引用包?对于项目引用,使用项目文件的  ProjectReference  元素上的  false  元数据会阻止将 dll 复制到输出。

若要正确引用  A.PluginBase  包,应将项目文件中的    元素更改为以下内容:

<PackageReference Include="A.PluginBase" Version="1.0.0"><ExcludeAssets>runtime</ExcludeAssets>
</PackageReference>

此操作会阻止将  A.PluginBase  程序集复制到插件的输出目录,并确保插件将使用 A 版本的  A.PluginBase。

插件目标框架建议

因为插件依赖项加载使用 .deps.json 文件,所以存在一个与插件的目标框架相关的问题 。具体来说,插件应该以运行时为目标,比如 .NET 5,而不是某一版本的 .NET Standard。.deps.json 文件基于项目所针对的框架生成,而且由于许多与 .NET Standard 兼容的包提供了用于针对 .NET Standard 进行生成的引用程序集和用于特定运行时的实现程序集,因此 .deps.json 可能无法正确查看实现程序集,或者它可能会获取 .NET Standard 版本的程序集,而不是期望的 .NET Core 版本的程序集。

插件框架引用

插件当前无法向该过程引入新的框架。例如,无法将使用  Microsoft.AspNetCore.App  框架的插件加载到只使用根  Microsoft.NETCore.App  框架的应用程序中。主机应用程序必须声明对插件所需的全部框架的引用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/293078.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

支持向量机SVC

原文&#xff1a; http://ihoge.cn/2018/SVWSVC.html 支持向量机(support vector machine)是一种分类算法&#xff0c;但是也可以做回归&#xff0c;根据输入的数据不同可做不同的模型&#xff08;若输入标签为连续值则做回归&#xff0c;若输入标签为分类值则用SVC()做分类&…

Beetlex官网迁移完成

由于beetlex.io域名无法指向国内&#xff0c;使用国内的服务器很多时候有抽风情况出现&#xff0c;所以把网站迁回国内&#xff1b;新的域名也申请完成并且申请备案通过&#xff0c;现在可以通过https://beetlex-io.com来访问Beetlex的官网.接下把涉及的费用和部署情况也说一下…

SVM支持向量机绘图

原文&#xff1a; http://ihoge.cn/2018/SVM绘图.html %matplotlib inline import matplotlib.pyplot as plt import numpy as np class1 np.array([[1, 1], [1, 3], [2, 1], [1, 2], [2, 2]]) class2 np.array([[4, 4], [5, 5], [5, 4], [5, 3], [4, 5], [6, 4]]) plt.f…

python列表生成多个号码_python遍历多个列表生成列表或字典

原博文 2017-03-10 18:30 − key[a,b,c,d] value[1,2,3,4] mydictdict(zip(key,value)) print mydict 输出结果&#xff1a; {a: 1, c: 3, b: 2, d: 4} 也可以用zip同时遍历多个列表&#xff0c;生成一个多维列表 key... 相关推荐 2019-12-18 21:27 − 一.zip函数描述和使用 zi…

NCC CAP 6.0 发布 —— 新增支持 OpenTelemetry

前言今天&#xff0c;我们很高兴宣布 CAP 发布 6.0 版本正式版&#xff0c;在这个版本中&#xff0c;我们主要致力于对 OpenTelemetry 提供支持&#xff0c;以及更好的适配 .NET 6。那么&#xff0c;接下来我们具体看一下吧。总览可能有些人还不知道 CAP 是什么&#xff0c;老规…

朴素贝叶斯--文档分类

原文&#xff1a;http://ihoge.cn/2018/MultinomialNB.html 把文档转换成向量 TF-IDF是一种统计方法&#xff0c;用以评估一个词语对于一份文档的重要程度。 TF表示词频&#xff0c; 即&#xff1a;词语在一片文档中出现的次数 词语总数IDF表示一个词的逆向文档频率指数&am…

Linux I/O 模型(待修改)

2019独角兽企业重金招聘Python工程师标准>>> 最近看到“服务器并发处理能力”章节&#xff0c;被里面的“I/O模型“搞得有点头晕&#xff0c;所以这里希望通过概念的辨析和对比&#xff0c;能更好的理解Linux的 I/O模型。 同步&#xff08;synchronous&#xff09;…

git代码提交流程

从master创建任务分支1.需要先将master分支代码更新到最新然后再切新分支&#xff1b;2.新需求和hotfix需要从master切分支&#xff0c;若是在QA测试阶段或者预发布阶段的bug&#xff0c;则需要再该功能分支上进行修改&#xff1b;提交代码到自己的任务分支commit之后一定要pus…

PCA主成分分析+SVM实现人脸识别

原文地址&#xff1a; http://ihoge.cn/2018/PCASVM人脸识别.html 加载数据 这里使用的测试数据共包含40位人员照片&#xff0c;每个人10张照片。也可登陆http://www.cl.cam.ac.uk/research/dtg/attarchive/facesataglance.html 查看400张照片的缩略图。 import time impo…

龙芯发布.NET 6.0.100开发者内测版

龙芯在龙芯开源社区发布了LoongArch64-.NET-SDK-6.0.100开发者内测版的新闻 &#xff0c;龙芯.NET基于上游社区 版本 适配支持龙芯平台架构。目前支持LoongArch64架构和MIPS64架构&#xff0c;LoongArch64架构的.NET-SDK-3.1已完成&#xff0c;安装包下载地址LoongArch64-.NET …

数据挖掘的9大成熟技术和应用

http://ihoge.cn/2018/DataMining.html 数据挖掘的9大成熟技术和应用 基于数据挖掘的9大主要成熟技术以及在数据化运营中的主要应用&#xff1a; 1、决策树 2、神经网络 3、回归 4、关联规则 5、聚类 6、贝叶斯分类 7、支持向量机 8、主成分分析 9、假设检验 1 决…

LVS:三种负载均衡方式与八种均衡算法

1、什么是LVS&#xff1f; 首先简单介绍一下LVS (Linux Virtual Server)到底是什么东西&#xff0c;其实它是一种集群(Cluster)技术&#xff0c;采用IP负载均衡技术和基于内容请求分发技术。调度器具有很好的吞吐率&#xff0c;将请求均衡地转移到不同的服务器上执行&#xff0…

排查 .NET开发的工厂MES系统 内存泄漏分析

一&#xff1a;背景 1. 讲故事上个月有位朋友加微信求助&#xff0c;说他的程序跑着跑着就内存爆掉了&#xff0c;寻求如何解决&#xff0c;截图如下&#xff1a;从聊天内容看&#xff0c;这位朋友压力还是蛮大的&#xff0c;话说这貌似是我分析的第三个 MES 系统了&#xff0c…

DataGirdView 常用操作

1、将数据源的某列添加到已有DataGirdView的列 例如&#xff1a;将文件夹下所有文件名添加到DataGirdView 的文件名一列&#xff0c;图片如下&#xff1a; 首先在datagridview把文件名列的DATAPROPERTYNAME设为你要显示的数据列的名字.此处我绑定的是folder.Name,所以直接在DAT…

Android之Android studio Gradle sync failed: Unknown host ‘services.gradle.org

错误描述&#xff1a; Gradle sync failed: Unknown host services.gradle.org. You may need to adjust the proxy settings in Gradle.Consult IDE log for more details (Help | Show Log)解决办法&#xff1a; 下载gradlectrlalts 然后输入gradle&#xff1b;在project-…

使用aconda3-5.1.0(Python3.6.4) 搭建pyspark远程部署

参考&#xff1a;http://ihoge.cn/2018/anacondaPyspark.html 前言 首次安装的环境搭配是这样的&#xff1a; jdk8 hadoop2.6.5 spark2.1 scala2.12.4 Anaconda3-5.1.0 一连串的报错让人惊喜无限&#xff0c;尽管反复调整配置始终无法解决。 坑了一整天后最后最终发现…

假如人类长出翅膀,会变成这种怪样子

鲁迅曾经说过&#xff1a;“不会画漫画的段子手不是好英语老师”咳咳~ 图图君就是这样一位专注知识科普的双语漫画家长按二维码带你去图图君家串串门儿想知道人类长出翅膀的怪样子吗&#xff1f;长按二维码关注回复“翅膀”寻找答案吧在这里你不仅可以大口呼吸知识还能顺便学个…

CSS3实战开发: 纯CSS实现图片过滤分类显示特效

CSS3实战开发: 纯CSS实现图片过滤分类显示特效 原文:CSS3实战开发: 纯CSS实现图片过滤分类显示特效各位网友大家好&#xff0c;今天我要带领大家开发一个纯CSS的图片分类显示的网址导航&#xff0c;单纯看标题大家可能有些困惑&#xff0c;依照以往惯例&#xff0c;我先给大家演…