介绍
McMaster.NET 是由开发者 Andrew Lock(@andrewlocknet) 创建和维护的一系列高质量、开源的 .NET 库的集合。
这些库专注于解决 .NET 开发中常见的痛点,提供简洁、高效且符合最佳实践的解决方案。它们通常设计为轻量级、模块化,并且与现有的 .NET 生态系统(如依赖注入、配置、日志等)无缝集成。
核心特点与价值
- 高质量与实用性:每个库都旨在解决一个具体、常见的问题,代码质量高,经过充分测试。
- 符合 .NET 标准:库的设计理念与 .NET 官方框架(如
Microsoft.Extensions.*)保持一致,学习成本低,易于集成。 - 开源与社区驱动:所有库都在 GitHub 上开源,接受社区贡献,问题响应和更新速度快。
- 文档完善:每个库都有详细的 README 和使用文档,上手非常容易。
详细介绍:
示例
首先,我们需要先安装对应的库!
# 宿主项目
dotnet add package McMaster.NETCore.Plugins
# 共享契约库(可选,插件和宿主都引用)
dotnet add package McMaster.NETCore.Plugins
项目结构
Solution/
├── MyApp/ # 宿主应用
│ ├── MyApp.csproj
│ ├── Program.cs
│ └── Plugins/ # 插件部署目录
│ └── DemoA/
│ └── DemoA.dll
│
├── PluginSDK/ # 共享契约(类库)
│ ├── Plugin.Contracts.csproj
│ ├── IPlugin.cs
│ ├── IHookablePlugin.cs
│ └── PluginMetadata.cs
│
└──── DemoA/ # 插件 A(类库)
├── DemoA.csproj
└── DemoA.cs
![图片[1]-[McMaster.NET 的使用] 打造自己的插件系统!-资源刺客](https://images.kodo.cdn.itdka.cn/wp-contents/uploads/2026/04/20260408201823437-image.png)
创建共享契约库 PluginSDK
这里我都是重新创建了一个 Dll 动态链接库,以便后续引用使用!
先创建接口,统一入口函数和类,方便调用!
namespace PluginSDK.Interface
{
/// <summary>
/// 插件基础信息接口
/// </summary>
public interface IPlugin
{
/// <summary>
/// 插件基本信息
/// </summary>
PluginMetadata Metadata { get; }
/// <summary>
/// 插件初始化方法,在插件被加载时调用(异步)
/// </summary>
/// <param name="services">DI 容器,获取所需的服务</param>
/// <param name="ct">逐个取消服务(默认值:不传用 CancellationToken.None)</param>
/// <returns></returns>
Task InitializeAsync(IServiceProvider services, CancellationToken ct = default);
/// <summary>
/// 插件服务配置
/// </summary>
/// <param name="services">插件服务集合</param>
void ConfigureServices(IServiceCollection services);
}
}
它是依赖注入容器的服务获取器,通过它可以从容器中取出任何已注册的服务。
由于插件是由 Activator.CreateInstance() 创建的,故DI 容器无法自动注入参数。
作用:Activator.CreateInstance(Type type) 通过反射在运行时动态创建对象实例。
// 编译时不知道具体类型
// 插件类型在运行时从 DLL 中加载
Type pluginType = assembly.GetType("MyPluginNamespace.MyPlugin");
// 无法直接 new(编译时不知道类型)
// var plugin = new MyPlugin(); // 编译错误
// 只能用反射动态创建
var plugin = Activator.CreateInstance(pluginType);
创建无参数构造函数
// 使用示例
Type pluginType = typeof(MyPlugin);
var instance = Activator.CreateInstance(pluginType);
// 转换为具体类型
if (instance is MyPlugin plugin)
{
plugin.DoSomething();
}
创建带参数构造函数
// 指定构造函数参数
object? CreateInstance(Type type, params object?[]? args);
// 使用示例
var instance = Activator.CreateInstance(
pluginType,
new object[] { "参数1", 123 } // 构造函数参数
);
创建指定绑定和上下文
// 更高级的重载
object? CreateInstance(
Type type,
BindingFlags bindingAttr,
Binder? binder,
object?[]? args,
CultureInfo? culture
);
// 使用示例:调用私有构造函数
var instance = Activator.CreateInstance(
pluginType,
BindingFlags.NonPublic | BindingFlags.Instance,
null,
new object[] { "私有参数" },
null
);
| 属性 / 方法 | 说明 |
| GetService<T>(this IServiceProvider provider) | 获取指定类型的服务,如果服务不存在返回 null 。 |
| GetRequiredService<T>(this IServiceProvider provider) | 获取必需的服务,如果不存在抛异常。 |
| GetServices<T>(this IServiceProvider provider) | 获取指定类型的所有实现(多个实现)。 |
| CreateScope(this IServiceProvider provider) | 创建新的作用域,用于管理生命周期。 |
| GetRequiredService(Type) | 非泛型版本的必需服务获取。 |
// 使用示例
var plugins = services.GetServices<IPlugin>();
foreach (var plugin in plugins)
{
Console.WriteLine(plugin.Metadata.Name);
}
// 使用示例
var plugins = services.GetServices<IPlugin>();
foreach (var plugin in plugins)
{
Console.WriteLine(plugin.Metadata.Name);
}
// 使用示例
using (var scope = services.CreateScope())
{
var scopedService = scope.ServiceProvider.GetService<IMyService>();
// 使用 scopedService
} // 作用域结束,释放资源
// 使用示例
var serviceType = typeof(IMyService);
var service = services.GetRequiredService(serviceType);
namespace PluginSDK.Interface
{
/// <summary>
/// 钩子插件接口,继承 IPlugin 接口
/// </summary>
public interface IHookablePlugin : IPlugin
{
/// <summary>
/// 钩子列表
/// </summary>
IReadOnlyList<HookPoints> HookPoints { get; }
/// <summary>
/// 钩子函数(强运行结果)
/// </summary>
/// <param name="hookPoint">钩子</param>
/// <param name="context">上下文对象,包含环境信息</param>
/// <param name="ct">逐个取消服务(默认值:不传用 CancellationToken.None)</param>
/// <returns>返回插件的运行结果</returns>
Task<PluginResult> ExecutesAsync(HookPoints hookPoint, IPluginContext context, CancellationToken ct = default);
/// <summary>
/// 钩子函数(不返回结果)
/// </summary>
/// <param name="hookPoint">钩子</param>
/// <param name="context">上下文对象,包含环境信息</param>
/// <param name="ct">逐个取消服务(默认值:不传用 CancellationToken.None)</param>
/// <returns></returns>
Task ExecuteAsync(HookPoints hookPoint, IPluginContext context, CancellationToken ct = default);
}
}
作用:只读的列表接口,提供索引访问和元素数量,但不能修改集合内容。
示例:
//不好的做法:返回 List<T>
public List<HookPoints> GetHookPoints()
{
return _hookPoints; // 外部可以修改!
}
//好的做法:返回 IReadOnlyList<T>
public IReadOnlyList<HookPoints> GetHookPoints()
{
return _hookPoints; // 外部只能读,不能修改
}
创建语法
// 方法1:使用 AsReadOnly()
List<HookPoints> list = new List<HookPoints>
{
HookPoints.ApplicationHook.AppStart,
HookPoints.UserHook.AfterLogin,
};
IReadOnlyList<HookPoints> readOnly = list.AsReadOnly();
// 方法2:直接赋值(隐式转换)
IReadOnlyList<HookPoints> readOnly2 = list;
常用属性
IReadOnlyList<HookPoints> hooks = GetHooks();
// 获取元素数量
int count = hooks.Count;
Console.WriteLine($"钩子数量: {count}"); // 输出: 钩子数量: 3
IReadOnlyList<HookPoints> hooks = GetHooks();
// 通过索引访问元素
HookPoints firstHook = hooks[0]; // 第一个钩子
HookPoints secondHook = hooks[1]; // 第二个钩子
// 注意:索引越界会抛出 IndexOutOfRangeException
// HookPoints invalid = hooks[999]; // 抛出异常
常用方法
IReadOnlyList<HookPoints> hooks = GetHooks();
// 方法1:foreach 遍历(最常用)
foreach (var hook in hooks)
{
Console.WriteLine(hook.ToString());
}
// 方法2:显式使用枚举器
var enumerator = hooks.GetEnumerator();
while (enumerator.MoveNext())
{
var hook = enumerator.Current;
Console.WriteLine(hook.ToString());
}
enumerator.Dispose();
IReadOnlyList<HookPoints> hooks = GetHooks();
// 复制到数组
HookPoints[] array = new HookPoints[hooks.Count];
hooks.CopyTo(array, 0); // 从数组的0位置开始复制
// 复制到数组的指定位置
HookPoints[] targetArray = new HookPoints[hooks.Count + 2];
hooks.CopyTo(targetArray, 1); // 从数组的1位置开始复制
using System.Linq;
IReadOnlyList<HookPoints> hooks1 = GetHooks();
IReadOnlyList<HookPoints> hooks2 = GetHooks();
// 比较两个集合是否相同(顺序和内容都要相同)
bool equal = hooks1.SequenceEqual(hooks2);
Console.WriteLine($"集合是否相等: {equal}");
作用:.NET 中用于协作式取消异步操作的机制,允许在操作执行过程中响应取消请求。
// 强制取消:直接终止线程(危险)
Thread.Abort(); // 已废弃,不推荐
// 协作式取消:操作自己决定何时退出
if (cancellationToken.IsCancellationRequested)
{
// 清理资源
// 退出操作
}
应用场景
// 场景1:应用关闭时取消长时间操作
public async Task ProcessDataAsync(CancellationToken ct)
{
for (int i = 0; i < 1000000; i++)
{
ct.ThrowIfCancellationRequested(); // 检查取消
await ProcessItem(i);
}
}
// 场景2:用户取消请求
public async Task DownloadFileAsync(string url, CancellationToken ct)
{
using var httpClient = new HttpClient();
// 下载过程中检查取消
var response = await httpClient.GetAsync(url, ct);
// ...
}
// 场景3:超时控制
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); // 30秒超时
await LongRunningOperationAsync(cts.Token);
常用属性
// 返回一个永远不会被取消的 CancellationToken
CancellationToken none = CancellationToken.None;
// 使用示例
public Task DoWorkAsync(CancellationToken ct = default)
{
// 如果调用者不传 CancellationToken,会使用默认值 CancellationToken.None
// 这意味着操作永远不会被取消
return Task.Delay(1000, ct);
}
// 检查是否已被请求取消
public async Task WorkAsync(CancellationToken ct)
{
for (int i = 0; i < 100; i++)
{
// 检查取消状态
if (ct.IsCancellationRequested)
{
Console.WriteLine("操作被取消");
break;
}
await Task.Delay(100);
}
}
// 检查此 CancellationToken 是否可以被取消
CancellationToken ct = CancellationToken.None;
bool canBeCanceled = ct.CanBeCanceled; // false
var cts = new CancellationTokenSource();
bool canBeCanceled2 = cts.Token.CanBeCanceled; // true
// 获取等待句柄,用于同步等待取消
var cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
// 等待取消(阻塞当前线程)
bool cancelled = ct.WaitHandle.WaitOne(TimeSpan.FromSeconds(5));
if (cancelled)
{
Console.WriteLine("操作被取消");
}
常用方法
// 如果已被取消,抛出 OperationCanceledException
public async Task ProcessAsync(CancellationToken ct)
{
for (int i = 0; i < 1000; i++)
{
// 自动检查并抛出异常
ct.ThrowIfCancellationRequested();
await ProcessItem(i);
}
}
// 注册取消时的回调
public async Task WorkAsync(CancellationToken ct)
{
// 注册回调:取消时执行
using (ct.Register(() => Console.WriteLine("操作被取消了")))
{
for (int i = 0; i < 100; i++)
{
if (ct.IsCancellationRequested)
break;
await Task.Delay(100);
}
}
}
// 带自定义异常的重载
ct.ThrowIfCancellationRequested(() => new CustomException("操作被取消"));
CancellationTokenSource
// 创建 CancellationTokenSource
var cts = new CancellationTokenSource();
// 获取 CancellationToken
CancellationToken ct = cts.Token;
// 触发取消
cts.Cancel();
// 释放资源
cts.Dispose();
// 创建超时取消源(30秒后自动取消)
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
// 或者
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(30)); // 30秒后自动取消
// 使用
await LongRunningOperationAsync(cts.Token);
// 创建链式取消源
var parent CancellationTokenSource.CreateLinkedTokenSource(parentCts.Token);
// 任何一个取消,另一个也会取消
parentCts.Cancel(); // childCts 也会被取消
childCts.Cancel(); // parentCts 不受影响
这里还需要创建一个请求管道,用于 Http 请求的时候使用!
namespace PluginSDK.Interface
{
/// <summary>
/// 请求管道插件接口
/// </summary>
public interface IRequestPlugin:IPlugin
{
/// <summary>
/// 执行顺序(权重)
/// </summary>
int Order { get; }
/// <summary>
/// 请求管道插件方法,在每次 HTTP 请求时调用,允许插件在请求处理过程中执行自定义逻辑(异步)
/// </summary>
/// <param name="context">当前 http 的所有信息</param>
/// <param name="next">http 请求处理对象</param>
Task InvokeAsync(HttpContext context, RequestDelegate next);
}
}
作用:ASP.NET Core 中间件管道的核心委托,它表示一个处理 HTTP 请求的方法。
核心作用:
中间件管道的构建块
// 中间件管道示意图
RequestDelegate pipeline = endpoint; // 最终的端点处理
// 包装一层中间件
pipeline = new LoggingMiddleware(pipeline).InvokeAsync;
// 再包装一层
pipeline = new AuthenticationMiddleware(pipeline).InvokeAsync;
// 最终的管道
pipeline = new ExceptionHandlerMiddleware(pipeline).InvokeAsync;
![图片[2]-[McMaster.NET 的使用] 打造自己的插件系统!-资源刺客](https://images.kodo.cdn.itdka.cn/wp-contents/uploads/2026/04/20260409212506152-image.png)
常用属性:
public async Task MyMiddleware(HttpContext context, RequestDelegate next)
{
// ========== 请求相关 ==========
HttpRequest request = context.Request;
// 请求方法
string method = request.Method; // GET, POST, PUT, DELETE 等
// 请求路径
string path = request.Path; // /api/users/123
string pathBase = request.PathBase; // 基础路径
// 查询字符串
string queryString = request.QueryString.Value; // ?name=john&age=30
string name = request.Query["name"]; // 获取单个参数
// 请求头
string userAgent = request.Headers["User-Agent"];
string authorization = request.Headers.Authorization;
// 请求体
string body = await new StreamReader(request.Body).ReadToEndAsync();
// ========== 响应相关 ==========
HttpResponse response = context.Response;
// 设置状态码
response.StatusCode = 200;
// 设置响应头
response.Headers["X-Custom-Header"] = "value";
// 写入响应体
await response.WriteAsync("Hello World");
// ========== 其他 ==========
// 路由参数
var routeValues = context.GetRouteData().Values;
// 用户信息
var user = context.User;
// 请求服务
var logger = context.RequestServices.GetService<ILogger<MyMiddleware>>();
// 调用下一个中间件
await next(context);
}
还有插件上下文,获取 DI 注入器,用于系统注入使用!
namespace PluginSDK.Interface
{
/// <summary>
/// 插件执行上下文
/// </summary>
public interface IPluginContext
{
/// <summary>
/// 执行代码时,获取所需的服务
/// </summary>
IServiceProvider Services { get; }
/// <summary>
/// 数据传递
/// </summary>
IDictionary<string, object> Data { get; }
}
}
插件钩子与信息对象
插件信息对象:用于获取插件的信息!
namespace PluginSDK.Models
{
/// <summary>
/// 插件基本信息实体模型
/// </summary>
public record PluginMetadata
{
//init:只能在初始化对象的时候进行赋值
/// <summary>
/// 插件ID
/// </summary>
public required string PluginID { get; init; }
/// <summary>
/// 插件名称
/// </summary>
public required string PluginName { get; init; }
/// <summary>
/// 插件版本号
/// </summary>
public required string Version { get; init; }
/// <summary>
/// 插件介绍
/// </summary>
public string? Introduce { get; init; }
/// <summary>
/// 插件作者
/// </summary>
public required string Author { get; init; }
}
}
插件钩子:用于指定某些函数在那些位置执行
HookPoints 类为主要的钩子对象,其使用结构体来实现,可用于分文件分类创建钩子对象,方便管理。
namespace PluginSDK.Models
{
/// <summary>
/// 钩子点
/// </summary>
public partial struct HookPoints : IEquatable<HookPoints>
{
/// <summary>
/// 钩子分类
/// </summary>
public string Category { get; }
/// <summary>
/// 钩子名称
/// </summary>
public string Name { get; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="category">钩子分类</param>
/// <param name="name">钩子名称</param>
internal HookPoints(string category, string name)
{
Category = category;
Name = name;
}
///// <summary>
///// 钩子点创建函数
///// </summary>
///// <param name="category">钩子分类</param>
///// <param name="name">钩子名称</param>
//internal static HookPoint Create(string category, string name) => new(category, name);
/// <summary>
/// 判断钩子是否相等
/// </summary>
/// <param name="other">输入的钩子</param>
/// <returns>判断结果</returns>
public bool Equals(HookPoints other) => Category == other.Category && Name == other.Name;
/// <summary>
/// 判断钩子是否相等
/// </summary>
/// <param name="other">输入的钩子</param>
/// <returns>判断结果</returns>
public override bool Equals(object? obj) => obj is HookPoints other && Equals(other);
/// <summary>
/// 重写 GetHashCode
/// </summary>
public override int GetHashCode() => HashCode.Combine(Category, Name);
/// <summary>== 运算符重载</summary>
public static bool operator ==(HookPoints left, HookPoints right) => left.Equals(right);
/// <summary>!= 运算符重载</summary>
public static bool operator !=(HookPoints left, HookPoints right) => !(left == right);
/// <summary>
/// 重写 ToString
/// </summary>
public override string ToString() => $"{Category}.{Name}";
}
}
分类的钩子对象:
namespace PluginSDK.Hooks
{
public partial struct HookPoint
{
/// <summary>
/// 应用生命周期钩子
/// </summary>
public class ApplicationHook
{
/// <summary>
/// 应用启动时触发
/// 触发时机:Program.cs 中 app.Run() 之前
/// </summary>
public static readonly HookPoints AppStart = new HookPoints("Application", "AppStart");
/// <summary>
/// 应用关闭时触发
/// 触发时机:用户按 Ctrl+C 或服务停止时
/// </summary>
public static readonly HookPoints AppShutdown = new HookPoints("Application", "AppShutdown");
}
}
}
namespace PluginSDK.Hooks
{
public partial struct HookPoint
{
/// <summary>
/// 请求管道钩子
/// </summary>
public class RequestHook
{
/// <summary>
/// 请求处理前触发
/// 触发时机:请求到达控制器之前
/// </summary>
public static readonly HookPoints BeforeRequest = new HookPoints("Request", "BeforeRequest");
/// <summary>
/// 请求处理后触发
/// 触发时机:控制器执行完毕,响应返回之前
/// </summary>
public static readonly HookPoints AfterRequest = new HookPoints("Request", "AfterRequest");
}
}
}
namespace PluginSDK.Hooks
{
public partial struct HookPoint
{
/// <summary>
/// 用户钩子
/// </summary>
public class UserHook
{
/// <summary>
/// 用户登录钩子(密码验证前)
/// </summary>
public static readonly HookPoints UserLoginBefore = new HookPoints("User", "LoginBefore");
/// <summary>
/// 用户登录钩子(密码验证后)
/// </summary>
public static readonly HookPoints UserLoginAfter = new HookPoints("User", "LoginAfter");
}
}
}
带返回值的调用接口模型
有时候需要插件返回数据,这时就需要一个专用的模型用于传递返回值!
namespace PluginSDK.Models;
/// <summary>
/// 插件返回的结果(基类)
/// </summary>
public class PluginResult
{
/// <summary>插件 ID</summary>
public string PluginID { get; set; } = "";
/// <summary>是否执行成功</summary>
public bool Success { get; set; } = true;
/// <summary>返回的消息</summary>
public string Message { get; set; } = "";
/// <summary>返回的自定义数据</summary>
public Dictionary<string, object> Data { get; set; } = new();
}
下面这个为带强类型的返回值,便于直接调用使用!开发者可以自定义返回值类型!
namespace PluginSDK.Models
{
/// <summary>
/// 插件返回的结果
/// </summary>
/// <typeparam name="T">结果的类型</typeparam>
public class PluginResults<T> : PluginResult
{
public T? Value { get; set; }
}
}
插件中自定义模型类:
public class LoginCheckResult
{
public bool AllowLogin { get; set; }
public string WelcomeMessage { get; set; } = "";
public int BonusPoints { get; set; }
}
宿主端获取自定义模型类中的数据:
var results = await _engine.TriggerAsync(HookPoints.UserHook.AfterLogin, data);
foreach (var result in results)
{
//强类型转换
if (result is PluginResult<LoginCheckResult> typed)
{
Console.WriteLine(typed.Value?.WelcomeMessage);
Console.WriteLine(typed.Value?.BonusPoints);
}
}
插件上下文
namespace PluginSystem
{
internal class DefaultPluginContext : IPluginContext
{
/// <summary>
/// 获取已注册的服务
/// </summary>
public IServiceProvider Services { get; }
/// <summary>
/// 共享的数据
/// </summary>
public IDictionary<string, object> Data { get; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="services">已注册的服务</param>
/// <param name="data">共享的数据</param>
public DefaultPluginContext(IServiceProvider services, IDictionary<string, object> data)
{
Services = services;
Data = data;
}
}
}
编写测试插件 Demo
namespace DemoA
{
internal class App : IHookablePlugin
{
/// <summary>
/// 插件日志对象
/// </summary>
private ILogger<HookDemo>? _logger;
/// <summary>
/// 钩子列表
/// </summary>
public IReadOnlyList<HookPoints> HookPoints =>
[
HookPoint.ApplicationHook.AppStart,
];
/// <summary>
/// 插件信息
/// </summary>
public PluginMetadata Metadata => new()
{
PluginID = "",//需要从系统中申请
PluginName = "系统插件 Demo",
Author = "后台管理员",
Introduce = "一个插件模板",
Version = "1.0",
};
/// <summary>
/// 依赖注入服务函数
/// </summary>
/// <param name="services">依赖注入服务变量</param>
public void ConfigureServices(IServiceCollection services)
{
}
/// <summary>
/// 主程序入口函数
/// </summary>
/// <param name="hookPoint">钩子函数点</param>
/// <param name="context">插件对象上下文</param>
/// <param name="ct">销毁对象</param>
/// <returns></returns>
public Task ExecuteAsync(HookPoints hookPoint, IPluginContext context, CancellationToken ct = default)
{
//var services = context.Services.GetRequiredService<DemoServices>();
_logger?.LogInformation("[Demo] 插件触发了 {hook}", hookPoint);
if (hookPoint == HookPoint.ApplicationHook.AppStart)
{
_logger?.LogInformation("[Demo] 插件触发了 {hook}!", hookPoint);
}
return Task.CompletedTask;
}
/// <summary>
/// 主程序入口函数(有返回值)
/// </summary>
/// <param name="hookPoint">钩子函数点</param>
/// <param name="context">插件对象上下文</param>
/// <param name="ct">销毁对象</param>
/// <typeparam name="TR">返回值类型["PluginResult","PluginResults<T>"]</typeparam>
/// <returns>运行返回值</returns>
/// 注意:在程序开始运行时,不会使用该函数;T为自定义的强类型返回值
public Task<PluginResult> ExecutesAsync(HookPoints hookPoint, IPluginContext context, CancellationToken ct = default)
{
return Task.FromResult(new PluginResult
{
Message = $"[Demo] 插件触发了 {hookPoint}!",
});
}
/// <summary>
/// 初始化函数
/// </summary>
/// <param name="services">系统服务对象</param>
/// <param name="ct">销毁对象</param>
/// <returns></returns>
public Task InitializeAsync(IServiceProvider services, CancellationToken ct = default)
{
return Task.CompletedTask;
}
}
}
插件引擎
这里我重新创建了一个 Dll 动态链接库!
namespace PluginSystem.System
{
public class PluginEngine : IDisposable
{
/// <summary>
/// 插件目录路径
/// </summary>
private readonly string _pluginsDir;
/// <summary>
/// 日志器
/// </summary>
private readonly ILogger<PluginEngine> _logger;
/// <summary>
/// 加载池
/// </summary>
private readonly List<PluginLoader> _loaders = new();
/// <summary>
/// 插件池
/// </summary>
private readonly List<IPlugin> _plugins = new();
/// <summary>
/// 目录映射为加载池
/// </summary>
private readonly Dictionary<string, PluginLoader> _dirToLoader = new();
/// <summary>
/// 目录名映射为插件对象
/// </summary>
private readonly Dictionary<string, List<IPlugin>> _dirToPlugins = new();
/// <summary>
/// 线程锁,防止并发重载
/// </summary>
private readonly SemaphoreSlim _reloadLock = new(1, 1);
/// <summary>
/// 必须共享的类型 — 宿主和插件看到的是同一份定义
/// </summary>
private static readonly Type[] SharedTypes = new[]
{
// 契约接口
typeof(IPlugin),
typeof(IHookablePlugin),
typeof(IRequestPlugin),
typeof(PluginMetadata),
typeof(IPluginContext),
// DI 相关
typeof(IServiceCollection),
typeof(IServiceProvider),
typeof(ServiceDescriptor),
// 日志
typeof(ILogger),
typeof(ILogger<>),
typeof(LogLevel),
// ASP.NET Core
typeof(HttpContext),
typeof(RequestDelegate),
typeof(HttpRequest),
typeof(HttpResponse),
// 常用基础类型
typeof(Task),
typeof(CancellationToken),
typeof(IDisposable),
};
/// <summary>
/// DI 容器
/// </summary>
private IServiceProvider? _serviceProvider;
/// <summary>
/// 保存 DI 容器的"构建器"引用,让热重载时新加载的插件也能注册自己的服务
/// </summary>
private IServiceCollection? _serviceCollection;
/// <summary>
/// 插件对象
/// </summary>
public IReadOnlyList<IPlugin> Plugins => _plugins;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="pluginsDir">插件目录路径</param>
/// <param name="logger">日志器</param>
public PluginEngine(string pluginsDir, ILogger<PluginEngine> logger)
{
_pluginsDir = pluginsDir;
_logger = logger;
}
/// <summary>
/// DI 容器
/// </summary>
/// <param name="sp">DI 容器对象</param>
public void SetServiceProvider(IServiceProvider sp) => _serviceProvider = sp;
/// <summary>
/// 暴露锁给 HotReloadService 使用
/// </summary>
public SemaphoreSlim ReloadLock => _reloadLock;
/// <summary>
/// DI 容器的"构建器"
/// </summary>
public IServiceProvider? ServiceProvider => _serviceProvider;
/// <summary>
/// 扫描并加载所有插件
/// </summary>
public void LoadAll()
{
if (!Directory.Exists(_pluginsDir))
{
Directory.CreateDirectory(_pluginsDir);
_logger.LogWarning("插件目录为空: {Dir}", _pluginsDir);
return;
}
// 每个插件一个子目录
foreach (var pluginDir in Directory.GetDirectories(_pluginsDir))
{
LoadPluginFromDirectory(pluginDir);
}
_logger.LogInformation("共加载 {Count} 个插件", _plugins.Count);
}
/// <summary>
/// 加载单个插件
/// </summary>
/// <param name="pluginDir">插件目录路径</param>
private void LoadPluginFromDirectory(string pluginDir)
{
var dirName = Path.GetFileName(pluginDir);
// 找到插件的主 DLL(排除契约库和系统库)
var mainDll = Directory.GetFiles(pluginDir, "*.dll")
.FirstOrDefault(f =>
{
var name = Path.GetFileNameWithoutExtension(f);
return !name.StartsWith("PluginSDK")//排除一下的插件
&& !name.StartsWith("Microsoft.")
&& !name.StartsWith("System.")
&& !name.StartsWith("netstandard");
});
if (mainDll == null || dirName != Path.GetFileNameWithoutExtension(mainDll))
{
_logger.LogWarning("目录 {Dir} 中未找到插件 DLL", dirName);
return;
}
try
{
var loader = PluginLoader.CreateFromAssemblyFile(
assemblyFile: mainDll,
config =>
{
//宿主负责加载的程序集
config.SharedAssemblies.Add(new AssemblyName("PluginSDK"));
config.SharedAssemblies.Add(new AssemblyName("Microsoft.Extensions.DependencyInjection"));
config.SharedAssemblies.Add(new AssemblyName("Microsoft.Extensions.DependencyInjection.Abstractions"));
config.SharedAssemblies.Add(new AssemblyName("Microsoft.Extensions.Logging"));
config.SharedAssemblies.Add(new AssemblyName("Microsoft.Extensions.Logging.Abstractions"));
config.SharedAssemblies.Add(new AssemblyName("Microsoft.AspNetCore.Http"));
config.SharedAssemblies.Add(new AssemblyName("Microsoft.AspNetCore.Http.Abstractions"));
config.IsUnloadable = true;//启用可卸载模式
}
);
//记录目录 → Loader 映射
_dirToLoader[dirName] = loader;
_loaders.Add(loader);//添加插件实例到加载池
var assembly = loader.LoadDefaultAssembly();//加载插件的主程序集
_logger.LogInformation("加载程序集: {Name}", assembly.GetName().Name);
var pluginTypes = assembly.GetTypes()//获取并检查是否有 IPlugin 接口的类或方法
.Where(t => typeof(IPlugin).IsAssignableFrom(t) && t.IsClass && !t.IsAbstract && !t.IsInterface && t.Name == "App");//不能是接口或是抽象类
// 记录此目录下的插件实例
var dirPlugins = new List<IPlugin>();
foreach (var type in pluginTypes)
{
if (Activator.CreateInstance(type) is IPlugin plugin)//创建反射实例并验证类型
{
_plugins.Add(plugin);
_logger.LogInformation("实例化插件: {Name} v{Version}", plugin.Metadata.PluginName, plugin.Metadata.Version);
}
}
// 记录目录 → 插件列表映射
_dirToPlugins[dirName] = dirPlugins;
}
catch (Exception ex)
{
_logger.LogError(ex, "加载插件失败: {Dir}", dirName);
}
}
/// <summary>
/// 卸载指定目录下的插件
/// </summary>
/// <param name="dirName">目录名</param>
public void UnloadPlugin(string dirName)
{
// 1. 找到该目录下的插件实例,从全局列表中移除
if (_dirToPlugins.TryGetValue(dirName, out var dirPlugins))
{
foreach (var plugin in dirPlugins)
{
_plugins.Remove(plugin);
}
_dirToPlugins.Remove(dirName);
}
// 2. 找到该目录的 Loader,释放它
if (_dirToLoader.TryGetValue(dirName, out var loader))
{
_loaders.Remove(loader);
loader.Dispose();
_dirToLoader.Remove(dirName);
}
_logger.LogInformation("已卸载插件: {Dir}", dirName);
}
/// <summary>
/// 将所有插件的 DI 服务注册到宿主容器
/// </summary>
/// <param name="services">插件服务</param>
public void ConfigureServices(IServiceCollection services)
{
_serviceCollection = services; //保存引用,热重载时用
foreach (var plugin in _plugins)
{
try
{
plugin.ConfigureServices(services);
_logger.LogInformation("[{Name}] 服务注册完成", plugin.Metadata.PluginName);
}
catch (Exception ex)
{
_logger.LogError(ex, "[{Name}] 服务注册失败", plugin.Metadata.PluginName);
}
}
// 将引擎自身注册为单例
services.AddSingleton(this);
}
/// <summary>
/// 初始化所有插件
/// </summary>
/// <param name="services">插件服务</param>
/// <param name="ct">插件实例销毁</param>
public async Task InitializeAllAsync(IServiceProvider services, CancellationToken ct = default)
{
foreach (var plugin in _plugins)
{
try
{
await plugin.InitializeAsync(services, ct);//逐个调用插件的初始化方法
}
catch (Exception ex)
{
_logger.LogError(ex, "[{Name}] 初始化失败", plugin.Metadata.PluginName);
}
}
}
/// <summary>
/// 触发钩子点
/// </summary>
/// <param name="hookPoint">钩子名称</param>
/// <param name="data">插件传入参数</param>
/// <param name="ct">插件实例销毁</param>
/// <typeparam name="TR">返回值类型["PluginResult","PluginResults"]</typeparam>
/// <returns>插件运行的返回值</returns>
public async Task<List<PluginResult>> TriggeresAsync(HookPoints hookPoint, IDictionary<string, object>? data = null, CancellationToken ct = default)
{
var hookPlugins = _plugins.OfType<IHookablePlugin>().Where(p => p.HookPoints.Contains(hookPoint));//筛选已实现 IHookablePlugin 接口的插件
var context = new DefaultPluginContext(_serviceProvider!, data ?? new Dictionary<string, object>());//注册服务
List<PluginResult> result = new List<PluginResult>();
foreach (var plugin in hookPlugins)//逐个加载插件
{
try
{
result.Add(await plugin.ExecutesAsync(hookPoint, context, ct));
}
catch (Exception ex)
{
_logger.LogError(ex, "[{Name}] 钩子 {Hook} 执行失败", plugin.Metadata.PluginName, hookPoint);
}
}
return result;
}
/// <summary>
/// 触发钩子点(不返回结果)
/// </summary>
/// <param name="hookPoint">钩子名称</param>
/// <param name="data">插件传入参数</param>
/// <param name="ct">插件实例销毁</param>
/// <returns></returns>
public async Task TriggerAsync(HookPoints hookPoint, IDictionary<string, object>? data = null, CancellationToken ct = default)
{
var hookPlugins = _plugins.OfType<IHookablePlugin>().Where(p => p.HookPoints.Contains(hookPoint));//筛选已实现 IHookablePlugin 接口的插件
var context = new DefaultPluginContext(_serviceProvider!, data ?? new Dictionary<string, object>());//注册服务
foreach (var plugin in hookPlugins)//逐个加载插件
{
try
{
await plugin.ExecuteAsync(hookPoint, context, ct);
}
catch (Exception ex)
{
_logger.LogError(ex, "[{Name}] 钩子 {Hook} 执行失败", plugin.Metadata.PluginName, hookPoint);
}
}
}
/// <summary>
/// 卸载所有插件
/// </summary>
public void Dispose()
{
//清空插件列表
_plugins.Clear();
_dirToPlugins.Clear();
foreach (var loader in _loaders)//依次释放加载器对象
{
loader.Dispose();
}
_loaders.Clear();//清空加载器
_dirToLoader.Clear();
//释放锁
_reloadLock.Dispose();
GC.Collect();//回收垃圾
GC.WaitForPendingFinalizers();//等待终结器执行完毕
_logger.LogInformation("所有插件已卸载");
}
/// <summary>
/// 从指定目录加载单个插件
/// </summary>
/// <param name="pluginDir">插件目录路径</param>
/// <param name="services">插件服务</param>
/// <param name="ct">插件实例销毁</param>
/// <returns></returns>
public async Task LoadPluginAsync(string pluginDir, IServiceProvider services, CancellationToken ct = default)
{
var dirName = Path.GetFileName(pluginDir);
// 1. 加载 DLL 并实例化
LoadPluginFromDirectory(pluginDir);
// 2. 注册新插件的 DI 服务
if (_dirToPlugins.TryGetValue(dirName, out var newPlugins))
{
foreach (var plugin in newPlugins)
{
try
{
plugin.ConfigureServices(_serviceCollection!);
_logger.LogInformation("[{Name}] 服务注册完成", plugin.Metadata.PluginName);
}
catch (Exception ex)
{
_logger.LogError(ex, "[{Name}] 服务注册失败", plugin.Metadata.PluginName);
}
}
// 3. 初始化新插件
foreach (var plugin in newPlugins)
{
try
{
await plugin.InitializeAsync(services, ct);
_logger.LogInformation("[{Name}] 初始化完成", plugin.Metadata.PluginName);
}
catch (Exception ex)
{
_logger.LogError(ex, "[{Name}] 初始化失败", plugin.Metadata.PluginName);
}
}
}
}
}
}
作用:.NET 依赖注入容器的核心接口,用于注册和管理应用程序中的服务。
核心作用:
服务注册中心
// 服务注册流程
IServiceCollection services = new ServiceCollection();
// 1. 注册服务
services.AddSingleton<IMyService, MyService>();
services.AddScoped<IRepository, Repository>();
services.AddTransient<IEmailService, EmailService>();
// 2. 构建服务提供者
IServiceProvider serviceProvider = services.BuildServiceProvider();
// 3. 解析服务
var myService = serviceProvider.GetService<IMyService>();
生命周期管理
// 三种生命周期
services.AddSingleton<ISingletonService, SingletonService>(); // 单例
services.AddScoped<IScopedService, ScopedService>(); // 作用域
services.AddTransient<ITransientService, TransientService>(); // 瞬态
常用方法
注册单例服务(整个应用程序生命周期内只创建一个实例)
// 方法1:类型注册
services.AddSingleton<IMyService, MyService>();
// 方法2:实例注册(直接提供实例)
var instance = new MyService();
services.AddSingleton<IMyService>(instance);
// 方法3:工厂方法注册
services.AddSingleton<IMyService>(sp => new MyService());
// 方法4:无接口注册
services.AddSingleton<MyService>();
// 使用示例
public class MyService : IMyService
{
private readonly int _id = Random.Shared.Next();
public int GetId() => _id;
}
// 在应用中
var service1 = serviceProvider.GetService<IMyService>();
var service2 = serviceProvider.GetService<IMyService>();
// service1 和 service2 是同一个实例
注册作用域服务(每个 HTTP 请求或每个作用域内创建一个实例)
// 方法1:类型注册
services.AddScoped<IRepository, Repository>();
// 方法2:工厂方法注册
services.AddScoped<IRepository>(sp =>
{
var dbContext = sp.GetService<MyDbContext>();
return new Repository(dbContext);
});
// 使用示例
public class Repository : IRepository
{
private readonly MyDbContext _dbContext;
public Repository(MyDbContext dbContext)
{
_dbContext = dbContext;
}
}
// 在 HTTP 请求中
public class UserController : Controller
{
private readonly IRepository _repository;
public UserController(IRepository repository)
{
_repository = repository; // 每个请求都是新实例
}
}
注册瞬态服务(每次请求都创建新实例)
// 方法1:类型注册
services.AddTransient<IEmailService, EmailService>();
// 方法2:工厂方法注册
services.AddTransient<IEmailService>(sp => new EmailService());
// 使用示例
public class EmailService : IEmailService
{
private readonly Guid _instanceId = Guid.NewGuid();
public Guid GetInstanceId() => _instanceId;
}
// 在应用中
var email1 = serviceProvider.GetService<IEmailService>();
var email2 = serviceProvider.GetService<IEmailService>();
// email1 和 email2 是不同的实例
// 注册 DbContext(EF Core 扩展方法)
services.AddDbContext<MyDbContext>(options =>
{
options.UseSqlServer(connectionString);
options.UseLoggerFactory(loggerFactory);
});
// 使用示例
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }
public DbSet<User> Users { get; set; }
}
// 在应用中
public class UserService
{
private readonly MyDbContext _dbContext;
public UserService(MyDbContext dbContext)
{
_dbContext = dbContext;
}
}
// 注册 HttpClient(扩展方法)
services.AddHttpClient<IMyApiService, MyApiService>(client =>
{
client.BaseAddress = new Uri("https://api.example.com");
client.Timeout = TimeSpan.FromSeconds(30);
client.DefaultRequestHeaders.Add("Accept", "application/json");
});
// 使用示例
public class MyApiService : IMyApiService
{
private readonly HttpClient _httpClient;
public MyApiService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<string> GetDataAsync()
{
var response = await _httpClient.GetAsync("/data");
return await response.Content.ReadAsStringAsync();
}
}
// 配置日志
services.AddLogging(builder =>
{
builder.AddConsole();
builder.AddDebug();
builder.AddEventSourceLogger();
// 配置日志级别
builder.SetMinimumLevel(LogLevel.Information);
// 添加文件日志
builder.AddFile("Logs/app-{Date}.log");
});
// 注册配置选项
services.AddOptions<MyOptions>()
.Bind(configuration.GetSection("MyOptions"))
.ValidateDataAnnotations() // 验证数据注解
.ValidateOnStart(); // 启动时验证
// 选项类
public class MyOptions
{
[Required]
public string ConnectionString { get; set; }
[Range(1, 100)]
public int MaxItems { get; set; } = 10;
}
// 在应用中
public class MyService
{
private readonly MyOptions _options;
public MyService(IOptions<MyOptions> options)
{
_options = options.Value;
}
}
系统注入
这里我在启动项目中创建了另外一个类,来方便管理 DI 注入!
namespace Services.DISerives
{
/// <summary>
/// 插件注入服务
/// </summary>
public class PluginDIServices
{
public async static void DISerivcesAfter(PluginEngine pluginEngine, WebApplication? app)
{
//设置 ServiceProvider 并初始化插件
pluginEngine.SetServiceProvider(app!.Services);
await pluginEngine.InitializeAllAsync(app!.Services);
//触发 ApplicationStart 钩子
await pluginEngine.TriggerAsync(HookPoint.ApplicationHook.AppStart);
//请求管道:BeforeRequest / AfterRequest
app.Use(async (ctx, next) =>
{
await pluginEngine.TriggerAsync(HookPoint.RequestHook.BeforeRequest,
new Dictionary<string, object> { ["HttpContext"] = ctx });
await next();
await pluginEngine.TriggerAsync(HookPoint.RequestHook.AfterRequest,
new Dictionary<string, object> { ["HttpContext"] = ctx });
});
//业务接口
app.MapGet("/p", () => Results.Ok(new
{
Message = "Plugin-Enabled App",
Plugins = pluginEngine.Plugins.Select(p => new
{
p.Metadata.PluginID,
p.Metadata.PluginName,
p.Metadata.Version
})
}));
//app.MapGet("/plugins", (PluginEngine engine) =>
//{
// return engine.Plugins.Select(p => new
// {
// p.Metadata.PluginID,
// p.Metadata.PluginName,
// p.Metadata.Version,
// Hooks = (p is IHookablePlugin hp) ? hp.HookPoints : Array.Empty<string>()
// });
//});
// 优雅关闭
app.Lifetime.ApplicationStopping.Register(async () =>
{
await pluginEngine.TriggerAsync(HookPoint.ApplicationHook.AppShutdown);
pluginEngine.Dispose();
});
}
public static PluginEngine DIServicesBefore(IServiceCollection services)
{
//初始化插件引擎
var pluginsDir = Path.Combine(AppContext.BaseDirectory, "SystemPlugins");
var pluginEngine = new PluginEngine(pluginsDir, services.BuildServiceProvider().GetRequiredService<ILogger<PluginEngine>>());
pluginEngine.LoadAll();
//插件注册 DI 服务
pluginEngine.ConfigureServices(services);
//返回插件引擎对象
return pluginEngine;
}
}
}
热重载
在真实项目中,插件的随插随用已是标配!
namespace PluginSystem.System
{
/// <summary>
/// 监听插件目录加载
/// </summary>
public class HotReloadService : BackgroundService
{
/// <summary>
/// 插件目录路径
/// </summary>
private readonly string _pluginsDir;
/// <summary>
/// 日志器
/// </summary>
private readonly ILogger<HotReloadService> _logger;
/// <summary>
/// 插件引擎
/// </summary>
private readonly PluginEngine _engine;
/// <summary>
/// 文件系统监视器
/// </summary>
private FileSystemWatcher? _watcher;
/// <summary>
/// 记录每个文件的防抖定时器
/// </summary>
private readonly Dictionary<string, CancellationTokenSource> _debouncers = new();
/// <summary>
/// 构造函数
/// </summary>
/// <param name="engine">插件引擎</param>
/// <param name="logger">日志器</param>
public HotReloadService(PluginEngine engine, ILogger<HotReloadService> logger)
{
_pluginsDir = Path.Combine(AppContext.BaseDirectory, "SystemPlugins");
_engine = engine;
_logger = logger;
}
/// <summary>
/// 系统启动自动调用
/// </summary>
/// <param name="stoppingToken">令牌(系统关闭时自动循环)</param>
/// <returns></returns>
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
_watcher = new FileSystemWatcher(_pluginsDir, "*.dll")//创建文件监视器并监视指定的目录
{
NotifyFilter = NotifyFilters.LastWrite//监听文件变化的修改时间、文件名、文件夹名
| NotifyFilters.FileName
| NotifyFilters.DirectoryName,
IncludeSubdirectories = true,//开启递归监视子目录
EnableRaisingEvents = true//开始改变后触发指定事件
};
// 防抖:避免短时间内多次触发
var debounce = new CancellationTokenSource();
_watcher.Changed += (s, e) =>//触发的事件
{
debounce.Cancel();//前面的事件取消
debounce = new CancellationTokenSource();//创建本次事件
Task.Delay(1000, debounce.Token).ContinueWith(_ =>//等待1s,如果被取消,则结束本次事件
{
if (!debounce.Token.IsCancellationRequested)
{
_logger.LogInformation("检测到插件文件变化: {File}", e.Name);
ReloadPlugin(e.FullPath);
}
});
};
_logger.LogInformation("热重载监听已启动: {Dir}", _pluginsDir);
return Task.Delay(Timeout.Infinite, stoppingToken);//返回无限等待
}
/// <summary>
/// 文件被修改或创建时触发
/// </summary>
/// <param name="sender">事件的调用者</param>
/// <param name="e">传递的参数</param>
private void OnFileChanged(object sender, FileSystemEventArgs e)
{
// 获取变化文件所在的目录名
var pluginDir = Path.GetDirectoryName(e.FullPath);
if (pluginDir == null) return;
var dirName = Path.GetFileName(pluginDir);
// 只处理插件目录下的 DLL(排除系统库)
var fileName = Path.GetFileNameWithoutExtension(e.Name ?? "");
if (fileName.StartsWith("Microsoft.") || fileName.StartsWith("System.") || fileName.StartsWith("Plugin.Contracts")) return;
//防抖:按目录名去重
if (_debouncers.TryGetValue(dirName, out var oldCts))
{
oldCts.Cancel();
oldCts.Dispose();
}
//取消之前的事件,创建新的事件
var newCts = new CancellationTokenSource();
_debouncers[dirName] = newCts;
//等待1s,如果没有新的事件,则执行重载
Task.Delay(1000, newCts.Token).ContinueWith(async _ =>
{
//如果在这1s内没有新的事件,则执行重载
if (!newCts.Token.IsCancellationRequested)
{
await ReloadSinglePlugin(dirName, pluginDir);
}
});
}
/// <summary>
/// 文件被删除时触发
/// </summary>
/// <param name="sender">事件的调用者</param>
/// <param name="e">传递的参数</param>
private void OnFileDeleted(object sender, FileSystemEventArgs e)
{
var pluginDir = Path.GetDirectoryName(e.FullPath);
if (pluginDir == null) return;
var dirName = Path.GetFileName(pluginDir);
_logger.LogWarning("检测到插件文件被删除: {Dir}", dirName);
// 只卸载,不重新加载
_engine.ReloadLock.Wait();
try
{
_engine.UnloadPlugin(dirName);
GC.Collect();
GC.WaitForPendingFinalizers();
}
finally
{
_engine.ReloadLock.Release();
}
}
/// <summary>
/// 只重载单个插件
/// </summary>
private async Task ReloadSinglePlugin(string dirName, string pluginDir)
{
// 加锁,防止并发重载
await _engine.ReloadLock.WaitAsync();
try
{
_logger.LogInformation("正在重载插件: {Dir}", dirName);
// 1. 卸载旧版本(只卸载这一个)
_engine.UnloadPlugin(dirName);
// 2. GC 回收,释放 DLL 文件锁
GC.Collect();
GC.WaitForPendingFinalizers();
// 3. 短暂等待,确保文件完全释放
await Task.Delay(100);
// 4. 加载新版本
await _engine.LoadPluginAsync(pluginDir,_engine.ServiceProvider!,CancellationToken.None);
_logger.LogInformation("插件重载完成: {Dir}", dirName);
}
catch (Exception ex)
{
_logger.LogError(ex, "插件热重载失败: {Dir}", dirName);
}
finally
{
_engine.ReloadLock.Release();
}
}
/// <summary>
/// 重新加载单个插件
/// </summary>
/// <param name="dllPath">插件文件路径</param>
private void ReloadPlugin(string dllPath)
{
try
{
var pluginDir = Path.GetDirectoryName(dllPath)!;//获取插件的目录路径
var dirName = Path.GetFileName(pluginDir);//获取插件目录名
_logger.LogInformation("正在重载插件: {Dir}", dirName);
// 卸载旧的(由 PluginEngine.Dispose 管理的 Loader)
// 这里简化处理:全部重载
_engine.Dispose();
//GC 回收
GC.Collect();
GC.WaitForPendingFinalizers();
//重新加载
_engine.LoadAll();
_logger.LogInformation("插件重载完成: {Dir}", dirName);
}
catch (Exception ex)
{
_logger.LogError(ex, "插件热重载失败: {Path}", dllPath);
}
}
/// <summary>
/// 垃圾回收
/// </summary>
public override void Dispose()
{
_watcher?.Dispose();
foreach (var cts in _debouncers.Values)
{
cts.Dispose();
}
_debouncers.Clear();
base.Dispose();
}
}
}
作用:.NET 中用于实现后台任务的抽象基类,它简化了长时间运行任务的开发。
核心作用
传统方式:手动管理生命周期
public class MyBackgroundTask : IHostedService
{
private Task _task;
private CancellationTokenSource _cts;
public Task StartAsync(CancellationToken cancellationToken)
{
_cts = new CancellationTokenSource();
_task = Task.Run(() => DoWork(_cts.Token));
return Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
_cts.Cancel();
await _task; // 等待任务完成
}
private void DoWork(CancellationToken ct) { /* ... */ }
}
使用 BackgroundService:简化开发
public class MyBackgroundTask : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// 执行后台任务
await Task.Delay(1000, stoppingToken);
}
}
}
应用场景
场景1:定时任务
public class ScheduledTaskService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// 每天凌晨执行
if (DateTime.Now.Hour == 0 && DateTime.Now.Minute == 0)
{
await DailyCleanup();
}
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
}
场景2:消息队列消费
public class QueueConsumerService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var message = await _queue.ReceiveAsync(stoppingToken);
await ProcessMessage(message);
}
}
}
场景3:文件监控(热重载)
public class FileWatcherService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using var watcher = new FileSystemWatcher();
watcher.Changed += OnFileChanged;
watcher.EnableRaisingEvents = true;
// 等待取消
await Task.Delay(Timeout.Infinite, stoppingToken);
}
}
常用方法
// 启动后台服务(由框架调用)
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// 创建 ExecuteAsync 任务
_executeTask = ExecuteAsync(_stoppingTokenSource.Token);
// 如果任务立即完成,返回 CompletedTask
if (_executeTask.IsCompleted)
{
return _executeTask;
}
// 否则返回 CompletedTask,任务在后台运行
return Task.CompletedTask;
}
在 Program.cs 中注册:builder.Services.AddHostedService<所属类类名>();
// 停止后台服务(由框架调用)
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
// 如果任务未启动,直接返回
if (_executeTask == null)
{
return;
}
// 触发取消
try
{
_stoppingTokenSource.Cancel();
}
finally
{
// 等待任务完成(最多等待 cancellationToken 指定的时间)
await Task.WhenAny(_executeTask, Task.Delay(Timeout.Infinite, cancellationToken));
}
}
// 在应用关闭时,框架会自动调用 StopAsync
app.Lifetime.ApplicationStopping.Register(() =>
{
// 可以在这里执行清理逻辑
Console.WriteLine("应用正在停止...");
});
public class TimerService : BackgroundService
{
private readonly ILogger<TimerService> _logger;
public TimerService(ILogger<TimerService> logger)
{
_logger = logger;
}
// 抽象方法,必须重写
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("定时服务启动");
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("执行定时任务: {Time}", DateTime.Now);
// 执行实际任务
await DoWork();
// 等待 5 秒
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
_logger.LogInformation("定时服务停止");
}
private async Task DoWork()
{
// 模拟工作
await Task.Delay(1000);
}
}
常用属性
存储 ExecuteAsync 返回的任务,用于在 StopAsync 中等待任务完成。
private Task? _executeTask;
生成取消令牌,传递给 ExecuteAsync 方法。
private readonly CancellationTokenSource _stoppingTokenSource = new();
// 在 ExecuteAsync 中使用
while (!stoppingToken.IsCancellationRequested)
{
// 执行任务
}
![[McMaster.NET 的使用] 打造自己的插件系统!-资源刺客](https://images.kodo.cdn.itdka.cn/wp-contents/uploads/2026/04/20260408212507422-科技感ENVI安装教程封面-1-scaled.png)

![[学习笔记 Day01]C++基础:简单的程序设计,始于梦想的开始!-资源刺客](http://images.kodo.cdn.itdka.cn/wp-content/uploads/2025/09/20250922171813209.webp)
![[学习笔记 Day02]Vue基础:前端造梦,继续干!-资源刺客](http://images.kodo.cdn.itdka.cn/wp-content/uploads/2025/09/20250919193418264.jpeg)
![[学习笔记 Day06]Python 数据分析与应用:数据分析库 Pandas 的使用-资源刺客](http://images.kodo.cdn.itdka.cn/wp-content/uploads/2025/09/20250923154612655.png)


![[学习笔记 Day01]Git 版本控制系统:Git 命令的学习!-资源刺客](http://images.kodo.cdn.itdka.cn/wp-content/uploads/2026/02/20260206152542723.png)
![[学习笔记 Day04]ASP.NET WEB 设计基础:让自己的程序飞向世界的每一个角落-资源刺客](http://images.kodo.cdn.itdka.cn/wp-content/uploads/2025/08/20250827185617438.jpeg)
![[Dotnet-ef 工具] ORM 工具生成实体模型步骤及常见问题!-资源刺客](https://images.kodo.cdn.itdka.cn/wp-contents/uploads/2026/03/20260326200207417-818064be8e4c5751f8ce12720bbecbfb.jpeg)

![[自动化 + 手残党专属]宝塔安装AllinSSL证书管理教程-资源刺客](http://images.kodo.cdn.itdka.cn/wp-content/uploads/2025/11/20251112122722716.png)



![[GitHub]Clawdbot-feishu:OpenClaw 的飞书集成插件-资源刺客](http://images.kodo.cdn.itdka.cn/wp-content/uploads/2026/02/20260214135015363.png)

暂无评论内容