背景

WebAPI 插件热插拔是指在不重启应用程序的情况下,能够动态地加载、更新或卸载功能模块(即插件)的能力。这种设计模式在软件开发中非常有用,尤其是在需要频繁更新或扩展功能的大型系统中。通过实现插件架构,可以将系统的不同部分解耦,使得它们可以独立开发、测试和部署。

对于WebAPI来说,这意味着服务端可以在运行时根据业务需求灵活调整其提供的API接口和服务逻辑,而无需担心每次修改都要重新启动整个应用,从而减少停机时间,提高系统的稳定性和灵活性。

程序演示

我们启动程序通过调用动态接口使用插件的增删改查等功能;,其中带{DynamicParam}的是你要使用的插件的名称;{funName}是你要使用插件的接口名称。

程序运行界面

查询筛选接口

使用postman 进行查询筛选博客操作

插件新增接口

插件的新增博客接口,然后看数据库变化

插件更新接口

插件的更新博客接口

插件删除接口

插件上传文件接口

代码实现

前提准备

需要安装nuget程序包:Newtonsoft.Json,SqlSugarCore。方便我们做类型转换和数据存储的相关功能;

1,首先我们创建一个webapi 的项目,然后定义一个插件IPluginDllApi.cs的接口(后续新增的类库需要继承用)

using Microsoft.AspNetCore.Mvc;

namespace DynamicPluginApiDemo.Utils
{
public interface IPluginDllApi
{
string Name { get; } IActionResult GetRequest(string funName, Dictionary<string, string> queryParameters); IActionResult PostRequestForm(string funName, Dictionary<string, string> queryParameters, IFormCollection formData); IActionResult PostRequestBody(string funName, Dictionary<string, string> queryParameters, object jsonData); }
}

2,创建一个插件帮助类和数据库帮助类。

using SqlSugar;

namespace DynamicPluginApiDemo.Utils
{
public static class DbHelper
{
public static SqlSugarClient Db;
static DbHelper()
{
if (Db == null)
//创建数据库对象 (用法和EF Dappper一样通过new保证线程安全)
Db = new SqlSugarClient(new ConnectionConfig()
{
ConnectionString = "server=192.168.1.61;Database=testdb;Uid=root;Pwd=MyNewPass@123;SslMode=None;AllowPublicKeyRetrieval=true;",
DbType = SqlSugar.DbType.MySql,
IsAutoCloseConnection = true
},
db =>
{
db.Aop.OnLogExecuting = (sql, pars) =>
{
//获取原生SQL推荐 5.1.4.63 性能OK
Console.WriteLine(UtilMethods.GetNativeSql(sql, pars));
}; });
}
}
} using System.Runtime.Loader; namespace DynamicPluginApiDemo.Utils
{
public class PluginDllHelper
{
public static List<IPluginDllApi> _labPlugins = new List<IPluginDllApi>();
private static string _pluginFolder = Path.Combine(Directory.GetCurrentDirectory(), "Plugins"); /// <summary>
/// 重新加载
/// </summary>
public static void ReLoadDll()
{
if (!Directory.Exists(_pluginFolder))
Directory.CreateDirectory(_pluginFolder); _labPlugins = new List<IPluginDllApi>();
foreach (var dllPath in Directory.GetFiles(_pluginFolder, "*.dll"))
{
try
{
var assemblyLoadContext = new AssemblyLoadContext(Path.GetFileNameWithoutExtension(dllPath), true);
var assembly = assemblyLoadContext.LoadFromAssemblyPath(dllPath); var pluginTypes = assembly.GetTypes()
.Where(t => typeof(IPluginDllApi).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract); foreach (var pluginType in pluginTypes)
{
if (Activator.CreateInstance(pluginType) is IPluginDllApi pluginInstance)
{
if (pluginInstance != null)
_labPlugins.Add((pluginInstance));
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Failed to load assembly {dllPath}: {ex.Message}");
}
}
}
}
}

3,然后我们在程序启动的时候进行调用

4,接下来我们创建一个PluginController.cs控制器,这个控制器实现了动态的路由。代码如下:

using DynamicPluginApiDemo.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq; namespace DynamicPluginApiDemo.Controllers
{
[Route("api/[controller]/{DynamicParam}")]
[ApiController]
public class PluginController : ControllerBase
{ /// <summary>
/// 动态插件的名称
/// </summary>
private readonly string _dynamicParam = string.Empty;
public PluginController(IHttpContextAccessor httpContextAccessor)
{
var dynamicParamKey = httpContextAccessor?.HttpContext?.GetRouteValue("DynamicParam");
if (dynamicParamKey != null)
_dynamicParam = dynamicParamKey?.ToString() ?? string.Empty;
} // GET: api/ApifulPlugin/apidll/GetRequest/Query?SearchName=AAA
[HttpGet("GetRequest/{functionName}")]
public IActionResult GetRequest(string functionName, [FromQuery] Dictionary<string, string> queryParameters)
{
var instance = PluginDllHelper._labPlugins.FirstOrDefault(x => x.Name == _dynamicParam);
if (instance == null)
return NotFound();
return instance.GetRequest(functionName, queryParameters);
} // POST: api/Dynamic/json
// Receives query parameters and JSON body
[HttpPost("PostRequestBody/{functionName}")]
public IActionResult PostRequestBody(string functionName, [FromQuery] Dictionary<string, string> queryParameters, [FromBody] object jsonData)
{
var instance = PluginDllHelper._labPlugins.FirstOrDefault(x => x.Name == _dynamicParam);
if (instance == null)
return NotFound();
return instance.PostRequestBody(functionName, queryParameters, jsonData);
} // POST: api/Dynamic/form
[HttpPost("PostRequestForm/{functionName}")]
public IActionResult PostRequestForm(string functionName, [FromQuery] Dictionary<string, string> queryParameters, IFormCollection formData)
{
var instance = PluginDllHelper._labPlugins.FirstOrDefault(x => x.Name == _dynamicParam);
if (instance == null)
return NotFound();
return instance.PostRequestForm(functionName, queryParameters, formData);
} }
}

5,webapi项目的内容差不多了。接下来我们创建一个类库项目,名字叫做BlogPluginApi。然后项目引用一下主项目,并且创建一个BlogPluginApi.cs文件继承主项目的IPluginDllApi。

using BlogPluginApi.Service;
using DynamicPluginApiDemo.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.SqlServer.Server; namespace BlogPluginApi
{
public class BlogPluginApi : IPluginDllApi
{
public string Name => "BlogPluginApi"; public IActionResult GetRequest(string funName, Dictionary<string, string> queryParameters)
{
var result = string.Empty;
switch (funName)
{
case "Query":
result = BlogService.Query(queryParameters);
break;
}
return new ContentResult
{
StatusCode = 200,
ContentType = "text/plain",
Content = result
};
} public IActionResult PostRequestBody(string funName, Dictionary<string, string> queryParameters, object jsonData)
{
var result = string.Empty;
switch (funName)
{
case "Save":
result = BlogService.Save(jsonData);
break;
case "Update":
result = BlogService.Update(jsonData);
break;
case "Delete":
result = BlogService.Delete(jsonData);
break;
}
return new ContentResult
{
StatusCode = 200,
ContentType = "text/plain",
Content = result
};
} public IActionResult PostRequestForm(string funName, Dictionary<string, string> queryParameters, IFormCollection formData)
{
var result = string.Empty;
switch (funName)
{
case "UploadFile":
result = BlogService.UploadFile(queryParameters, formData);
break;
}
return new ContentResult
{
StatusCode = 200,
ContentType = "text/plain",
Content = result
};
}
}
}

6,然后我们增加blog表的实体,服务,模型等。当然这些可以放在主项目中,通过项目引用使用主项目的代码。

using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace BlogPluginApi.Entitys.Blogs
{
[SugarTable("BLOG", TableDescription = "博客")]
public class Blog
{
[SugarColumn(ColumnName = "ID", IsIdentity = true, IsPrimaryKey = true)]
public int Id { get; set; } [SugarColumn(ColumnName = "Title")]
public string Title { get; set; } [SugarColumn(ColumnName = "Context")]
public string Context { get; set; } [SugarColumn(ColumnName = "UserId")]
public int UserId { get; set; } [SugarColumn(ColumnName = "CreateTime")] public DateTime CreateTime { get; set; }
}
} using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace BlogPluginApi.Models.Blogs
{
public class BlogInputDto
{
public int? Id { get; set; } public string? Title { get; set; } public string? Context { get; set; } public int? UserId { get; set; } public DateTime? CreateTime { get; set; }
}
} using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace BlogPluginApi.Models.Blogs
{
public class SearchDto
{
public string SearchName { get; set; }
}
} using AutoMapper;
using BlogPluginApi.Entitys.Blogs;
using BlogPluginApi.Models.Blogs;
using DynamicPluginApiDemo.Utils;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace BlogPluginApi.Service
{
public static class BlogService
{ public static string Query(Dictionary<string, string> queryParameters)
{
SearchDto searchBlogDto = new SearchDto();
if (queryParameters.TryGetValue("SearchName", out var searchName))
{
searchBlogDto.SearchName = searchName;
}
var result = string.Join(",", DbHelper.Db.Queryable<Blog>().Select(s => s.Title).ToList());
return result;
} public static string Delete(object param)
{
var strParam = param.ToString();
if (string.IsNullOrEmpty(strParam)) return string.Empty;
var deleteIds = JsonConvert.DeserializeObject<List<int>>(strParam);
if (deleteIds != null)
{
var count = DbHelper.Db.Deleteable<Blog>().Where(x => deleteIds.Contains(x.Id)).ExecuteCommand();
return count.ToString();
}
return string.Empty; } public static string Save(object param)
{
var strParam = param.ToString();
if (string.IsNullOrEmpty(strParam)) return string.Empty;
var inputDto = JsonConvert.DeserializeObject<Blog>(strParam);
if (inputDto != null)
{
var count = DbHelper.Db.Insertable(inputDto).ExecuteCommand();
}
return "success";
} public static string Update(object param)
{
var strParam = param.ToString();
if (string.IsNullOrEmpty(strParam)) return string.Empty;
var inputDto = JsonConvert.DeserializeObject<Blog>(strParam);
if (inputDto != null)
{
var count = DbHelper.Db.Updateable(inputDto).ExecuteCommand();
}
return "success";
} public static string UploadFile(Dictionary<string, string> queryParameters, IFormCollection formData)
{
string UploadId = string.Empty;
if (queryParameters.TryGetValue("uploadId", out var uploadId))
{
UploadId = uploadId;
} var file = formData.Files.FirstOrDefault();
if (file != null)
return file.FileName; return string.Empty;
} }
}

7,然后我们生成一下BlogPluginApi项目,把生成的dll文件放在放在主项目的Plugins文件夹下就可以了。

注意:

系统必须被设计为能够识别和管理不同的插件版本,并且能够在运行时安全地切换这些版本。

结语

Web API插件的热插拔是一个复杂但非常有价值的功能,它不仅提高了系统的灵活性和可用性,还增强了用户体验。通过精心规划和技术实践,可以使这一特性成为现代Web应用和服务的一个亮点。

C# WebAPI 插件热插拔的更多相关文章

  1. WebApi 插件式构建方案

    WebApi 插件式构建方案 WebApi 插件式构建方案 公司要推行服务化,不可能都整合在一个解决方案内,因而想到了插件式的构建方案.最终定型选择基于 WebApi 构建服务化,之所以不使用 WCF ...

  2. WebApi 插件式构建方案:重写的控制器获取工厂

    body { border: 1px solid #ddd; outline: 1300px solid #fff; margin: 16px auto; } body .markdown-body ...

  3. WebApi 插件式构建方案:IOC 容器初始化

    body { border: 1px solid #ddd; outline: 1300px solid #fff; margin: 16px auto; } body .markdown-body ...

  4. C# 插件热插拔 .NET:何时应该 “包装异常”? log4.net 自定义日志文件名称

    C# 插件热插拔   所谓热插拔就是插件可以 在主程序不重新启动的情况直接更新插件, 网上有很多方案: https://www.cnblogs.com/happyframework/p/3405811 ...

  5. C# 插件热插拔

    所谓热插拔就是插件可以 在主程序不重新启动的情况直接更新插件, 网上有很多方案: https://www.cnblogs.com/happyframework/p/3405811.html 如下: 但 ...

  6. WebApi 插件式构建方案:集成加载数据库连接字符串

    body { border: 1px solid #ddd; outline: 1300px solid #fff; margin: 16px auto; } body .markdown-body ...

  7. WebApi 插件式构建方案:发现并加载程序集

    插件式的 WebApi 开发,首要面对的问题就是程序集的发现.因为开发的过程中,都是在各自的解决方案下进行开发,部署后是分模块放在一个整体的的运行时网站下. 约定 这里我根据上一节的设定,把插件打包完 ...

  8. .NET MVC插件化开发(支持Script和css压缩)

    上一篇博文里面,没有支持Script和css的压缩功能以及script和css的路径问题也没有解决,所以重新发布一个版本,解决了这几个问题,并且优化了插件路由注册,现在可以很方便的实现热插拔web插件 ...

  9. 从零开始实现ASP.NET Core MVC的插件式开发(五) - 插件的删除和升级

    标题:从零开始实现ASP.NET Core MVC的插件式开发(五) - 使用AssemblyLoadContext实现插件的升级和删除 作者:Lamond Lu 地址:https://www.cnb ...

  10. IOC 容器初始化

    WebApi 插件式构建方案:IOC 容器初始化 一般来说,一个现代化的网站加载流程是这样的:程序集加载后,我们会初始化 IOC 容器,以便于接下来解析对象用. 我们插件式的开发,这一步更为重要.这是 ...

随机推荐

  1. 2023NOIP A层联测26 T3 tour

    2023NOIP A层联测26 T3 tour 有意思的树上主席树. 思路 首先考虑一个点 \(p\) 能计入答案的情况,就是 \(dis(x,p)-a_p \ge a_p\). 我们把 \(x \t ...

  2. laravel之验证器

    开发中使用框架自带验证器进行参数验证 1.定义验证器基类,定义失败返回值 新建基础类文件 app > Http > Requests > BaseRequest.php <?p ...

  3. PHP之项目环境变量设置

    需求 在PHP开发中为了区分线上生产环境还是本地开发环境, 如果我们能通过判断$_SERVER['RUNTIME_ENVIROMENT']为 'DEV'还是'PRO'来区分该多好, 可惜的是$_SER ...

  4. 2.3k Star!强得不像开源的问卷调研平台

    产品:咱们的新功能上线了,得问问用户的意见,做个调研问卷吧! 运营:对啊,用户意见很重要,我们要认真听取反馈! 领导:问卷别搞得像考试.我们要的是真实的声音,而不是让用户头疼的题目. 程序员:收到! ...

  5. 构建交互式聊天界面:react-chat-element 实战小计

    react聊天组件库:react-chat-elements 需求场景:用户可以通过多元的用户交互方式,如文件.图片.声音以及文字等输入相关信息,AI给出对应的回答 react-chat-elemen ...

  6. Tailwind CSS样式优先级控制

    前情 Tailwind CSS 是一个原子类 CSS 框架,它将基础的 CSS 全部拆分为原子级别,能达到最小化项目CSS.它的工作原理是扫描所有 HTML 文件.JavaScript 组件以及任何模 ...

  7. GooseFS 在云端数据湖存储上的降本增效实践

    ​ | 导语 基于云端对象存储的大数据和数据湖存算分离场景已经被广泛铺开,计算节点的独立扩缩容极大地优化了系统的整体运行和维护成本,云端对象存储的无限容量与高吞吐也保证了计算任务的高效和稳定.然而,云 ...

  8. Flutter null safety 无法运行

    Flutter空安全问题 在pub上有一些库导入之后无法运行,这是因为健全的空安全 解决方法 1.在命令行中添加参数 flutter run --no-sound-null-safety 2.在IDE ...

  9. 架构发展趋势以及 d2js 的未来

    目前架构有几个热点方向:微服务, dubbo, Faas,还有 TiDB. 现在开发模式是前后端分离基本成为行规. 应该说以大部分企业业务量级.人员规模来说,要去和淘宝等大厂去对标是非常傻的.对大部分 ...

  10. 关于 VMware 与 WSL 在 Win11 虚拟化的一些问题

    关于 VMware 与 WSL 在 Win11 虚拟化的一些问题 VMware 虚拟化问题 之前用虚拟机做计网 GNS3 组网实验的时候需要用到虚拟机虚拟化,然后一直显示虚拟化不成功,检查过 BIOS ...