背景
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应用和服务的一个亮点。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |