[McMaster.NET 的使用] 打造自己的插件系统!

介绍

McMaster.NET 是由开发者 Andrew Lock(@andrewlocknet) 创建和维护的一系列高质量、开源的 .NET 库的集合。

这些库专注于解决 .NET 开发中常见的痛点,提供简洁、高效且符合最佳实践的解决方案。它们通常设计为轻量级、模块化,并且与现有的 .NET 生态系统(如依赖注入、配置、日志等)无缝集成。

核心特点与价值

  1. 高质量与实用性:每个库都旨在解决一个具体、常见的问题,代码质量高,经过充分测试。
  2. 符合 .NET 标准:库的设计理念与 .NET 官方框架(如 Microsoft.Extensions.*)保持一致,学习成本低,易于集成。
  3. 开源与社区驱动:所有库都在 GitHub 上开源,接受社区贡献,问题响应和更新速度快。
  4. 文档完善:每个库都有详细的 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 的使用] 打造自己的插件系统!-资源刺客

创建共享契约库 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 的使用] 打造自己的插件系统!-资源刺客

常用属性:

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)
{
    // 执行任务
}

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容