C# WebAPI 插件热插拔

铁佛  金牌会员 | 2025-1-23 16:20:27 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 877|帖子 877|积分 2631

背景

WebAPI 插件热插拔是指在不重启应用步伐的情况下,可以或许动态地加载、更新或卸载功能模块(即插件)的能力。这种设计模式在软件开发中非常有效,尤其是在必要频繁更新或扩展功能的大型系统中。通过实现插件架构,可以将系统的差异部分解耦,使得它们可以独立开发、测试和摆设。
对于WebAPI来说,这意味着服务端可以在运行时根据业务需求灵活调整其提供的API接口和服务逻辑,而无需担心每次修改都要重新启动整个应用,从而减少停机时间,进步系统的稳定性和灵活性。
步伐演示

我们启动步伐通过调用动态接口利用插件的增删改查等功能;,其中带{DynamicParam}的是你要利用的插件的名称;{funName}是你要利用插件的接口名称。
步伐运行界面


 
 
查询筛选接口

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

 
 
 
插件新增接口

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

 

 
 
 
插件更新接口

插件的更新博客接口

 

 
 
插件删除接口


 

 
插件上传文件接口


 
代码实现

前提准备

必要安装nuget步伐包:Newtonsoft.Json,SqlSugarCore。方便我们做类型转换和数据存储的相关功能;
1,首先我们创建一个webapi 的项目,然后界说一个插件IPluginDllApi.cs的接口(后续新增的类库必要继承用)
  1. using Microsoft.AspNetCore.Mvc;
  2. namespace DynamicPluginApiDemo.Utils
  3. {
  4.     public interface IPluginDllApi
  5.     {
  6.         string Name { get; }
  7.         IActionResult GetRequest(string funName, Dictionary<string, string> queryParameters);
  8.         IActionResult PostRequestForm(string funName, Dictionary<string, string> queryParameters, IFormCollection formData);
  9.         IActionResult PostRequestBody(string funName, Dictionary<string, string> queryParameters, object jsonData);
  10.     }
  11. }
复制代码
2,创建一个插件资助类和数据库资助类。
  1. using SqlSugar;
  2. namespace DynamicPluginApiDemo.Utils
  3. {
  4.     public static class DbHelper
  5.     {
  6.         public static SqlSugarClient Db;
  7.         static DbHelper()
  8.         {
  9.             if (Db == null)
  10.                 //创建数据库对象 (用法和EF Dappper一样通过new保证线程安全)
  11.                 Db = new SqlSugarClient(new ConnectionConfig()
  12.                 {
  13.                     ConnectionString = "server=192.168.1.61;Database=testdb;Uid=root;Pwd=MyNewPass@123;SslMode=None;AllowPublicKeyRetrieval=true;",
  14.                     DbType = SqlSugar.DbType.MySql,
  15.                     IsAutoCloseConnection = true
  16.                 },
  17.                db =>
  18.                {
  19.                    db.Aop.OnLogExecuting = (sql, pars) =>
  20.                    {
  21.                        //获取原生SQL推荐 5.1.4.63  性能OK
  22.                        Console.WriteLine(UtilMethods.GetNativeSql(sql, pars));
  23.                    };
  24.                });
  25.         }
  26.     }
  27. }
  28. using System.Runtime.Loader;
  29. namespace DynamicPluginApiDemo.Utils
  30. {
  31.     public class PluginDllHelper
  32.     {
  33.         public static List<IPluginDllApi> _labPlugins = new List<IPluginDllApi>();
  34.         private static string _pluginFolder = Path.Combine(Directory.GetCurrentDirectory(), "Plugins");
  35.         /// <summary>
  36.         /// 重新加载
  37.         /// </summary>
  38.         public static void ReLoadDll()
  39.         {
  40.             if (!Directory.Exists(_pluginFolder))
  41.                 Directory.CreateDirectory(_pluginFolder);
  42.             _labPlugins = new List<IPluginDllApi>();
  43.             foreach (var dllPath in Directory.GetFiles(_pluginFolder, "*.dll"))
  44.             {
  45.                 try
  46.                 {
  47.                     var assemblyLoadContext = new AssemblyLoadContext(Path.GetFileNameWithoutExtension(dllPath), true);
  48.                     var assembly = assemblyLoadContext.LoadFromAssemblyPath(dllPath);
  49.                     var pluginTypes = assembly.GetTypes()
  50.                         .Where(t => typeof(IPluginDllApi).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);
  51.                     foreach (var pluginType in pluginTypes)
  52.                     {
  53.                         if (Activator.CreateInstance(pluginType) is IPluginDllApi pluginInstance)
  54.                         {
  55.                             if (pluginInstance != null)
  56.                                 _labPlugins.Add((pluginInstance));
  57.                         }
  58.                     }
  59.                 }
  60.                 catch (Exception ex)
  61.                 {
  62.                     Console.WriteLine($"Failed to load assembly {dllPath}: {ex.Message}");
  63.                 }
  64.             }
  65.         }
  66.     }
  67. }
复制代码
3,然后我们在步伐启动的时间进行调用

 
 4,接下来我们创建一个PluginController.cs控制器,这个控制器实现了动态的路由。代码如下:
  1. using DynamicPluginApiDemo.Utils;
  2. using Microsoft.AspNetCore.Http;
  3. using Microsoft.AspNetCore.Mvc;
  4. using Newtonsoft.Json.Linq;
  5. namespace DynamicPluginApiDemo.Controllers
  6. {
  7.     [Route("api/[controller]/{DynamicParam}")]
  8.     [ApiController]
  9.     public class PluginController : ControllerBase
  10.     {
  11.         /// <summary>
  12.         /// 动态插件的名称
  13.         /// </summary>
  14.         private readonly string _dynamicParam = string.Empty;
  15.         public PluginController(IHttpContextAccessor httpContextAccessor)
  16.         {
  17.             var dynamicParamKey = httpContextAccessor?.HttpContext?.GetRouteValue("DynamicParam");
  18.             if (dynamicParamKey != null)
  19.                 _dynamicParam = dynamicParamKey?.ToString() ?? string.Empty;
  20.         }
  21.         // GET: api/ApifulPlugin/apidll/GetRequest/Query?SearchName=AAA
  22.         [HttpGet("GetRequest/{functionName}")]
  23.         public IActionResult GetRequest(string functionName, [FromQuery] Dictionary<string, string> queryParameters)
  24.         {
  25.             var instance = PluginDllHelper._labPlugins.FirstOrDefault(x => x.Name == _dynamicParam);
  26.             if (instance == null)
  27.                 return NotFound();
  28.             return instance.GetRequest(functionName, queryParameters);
  29.         }
  30.         // POST: api/Dynamic/json
  31.         // Receives query parameters and JSON body
  32.         [HttpPost("PostRequestBody/{functionName}")]
  33.         public IActionResult PostRequestBody(string functionName, [FromQuery] Dictionary<string, string> queryParameters, [FromBody] object jsonData)
  34.         {
  35.             var instance = PluginDllHelper._labPlugins.FirstOrDefault(x => x.Name == _dynamicParam);
  36.             if (instance == null)
  37.                 return NotFound();
  38.             return instance.PostRequestBody(functionName, queryParameters, jsonData);
  39.         }
  40.         // POST: api/Dynamic/form
  41.         [HttpPost("PostRequestForm/{functionName}")]
  42.         public IActionResult PostRequestForm(string functionName, [FromQuery] Dictionary<string, string> queryParameters, IFormCollection formData)
  43.         {
  44.             var instance = PluginDllHelper._labPlugins.FirstOrDefault(x => x.Name == _dynamicParam);
  45.             if (instance == null)
  46.                 return NotFound();
  47.             return instance.PostRequestForm(functionName, queryParameters, formData);
  48.         }
  49.     }
  50. }
复制代码
5,webapi项目的内容差不多了。接下来我们创建一个类库项目,名字叫做BlogPluginApi。然后项目引用一下主项目,而且创建一个BlogPluginApi.cs文件继承主项目的IPluginDllApi。
  1. using BlogPluginApi.Service;
  2. using DynamicPluginApiDemo.Utils;
  3. using Microsoft.AspNetCore.Http;
  4. using Microsoft.AspNetCore.Mvc;
  5. using Microsoft.SqlServer.Server;
  6. namespace BlogPluginApi
  7. {
  8.     public class BlogPluginApi : IPluginDllApi
  9.     {
  10.         public string Name => "BlogPluginApi";
  11.         public IActionResult GetRequest(string funName, Dictionary<string, string> queryParameters)
  12.         {
  13.             var result = string.Empty;
  14.             switch (funName)
  15.             {
  16.                 case "Query":
  17.                     result = BlogService.Query(queryParameters);
  18.                     break;
  19.             }
  20.             return new ContentResult
  21.             {
  22.                 StatusCode = 200,
  23.                 ContentType = "text/plain",
  24.                 Content = result
  25.             };
  26.         }
  27.         public IActionResult PostRequestBody(string funName, Dictionary<string, string> queryParameters, object jsonData)
  28.         {
  29.             var result = string.Empty;
  30.             switch (funName)
  31.             {
  32.                 case "Save":
  33.                     result = BlogService.Save(jsonData);
  34.                     break;
  35.                 case "Update":
  36.                     result = BlogService.Update(jsonData);
  37.                     break;
  38.                 case "Delete":
  39.                     result = BlogService.Delete(jsonData);
  40.                     break;
  41.             }
  42.             return new ContentResult
  43.             {
  44.                 StatusCode = 200,
  45.                 ContentType = "text/plain",
  46.                 Content = result
  47.             };
  48.         }
  49.         public IActionResult PostRequestForm(string funName, Dictionary<string, string> queryParameters, IFormCollection formData)
  50.         {
  51.             var result = string.Empty;
  52.             switch (funName)
  53.             {
  54.                 case "UploadFile":
  55.                     result = BlogService.UploadFile(queryParameters, formData);
  56.                     break;
  57.             }
  58.             return new ContentResult
  59.             {
  60.                 StatusCode = 200,
  61.                 ContentType = "text/plain",
  62.                 Content = result
  63.             };
  64.         }
  65.     }
  66. }
复制代码
6,然后我们增加blog表的实体,服务,模型等。当然这些可以放在主项目中,通过项目引用利用主项目的代码。

 
  1. using SqlSugar;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace BlogPluginApi.Entitys.Blogs
  8. {
  9.     [SugarTable("BLOG", TableDescription = "博客")]
  10.     public class Blog
  11.     {
  12.         [SugarColumn(ColumnName = "ID", IsIdentity = true, IsPrimaryKey = true)]
  13.         public int Id { get; set; }
  14.         [SugarColumn(ColumnName = "Title")]
  15.         public string Title { get; set; }
  16.         [SugarColumn(ColumnName = "Context")]
  17.         public string Context { get; set; }
  18.         [SugarColumn(ColumnName = "UserId")]
  19.         public int UserId { get; set; }
  20.         [SugarColumn(ColumnName = "CreateTime")]
  21.         public DateTime CreateTime { get; set; }
  22.     }
  23. }
  24. using SqlSugar;
  25. using System;
  26. using System.Collections.Generic;
  27. using System.Linq;
  28. using System.Text;
  29. using System.Threading.Tasks;
  30. namespace BlogPluginApi.Models.Blogs
  31. {
  32.     public class BlogInputDto
  33.     {
  34.         public int? Id { get; set; }
  35.         public string? Title { get; set; }
  36.         public string? Context { get; set; }
  37.         public int? UserId { get; set; }
  38.         public DateTime? CreateTime { get; set; }
  39.     }
  40. }
  41. using System;
  42. using System.Collections.Generic;
  43. using System.Linq;
  44. using System.Text;
  45. using System.Threading.Tasks;
  46. namespace BlogPluginApi.Models.Blogs
  47. {
  48.     public class SearchDto
  49.     {
  50.         public string SearchName { get; set; }
  51.     }
  52. }
  53. using AutoMapper;
  54. using BlogPluginApi.Entitys.Blogs;
  55. using BlogPluginApi.Models.Blogs;
  56. using DynamicPluginApiDemo.Utils;
  57. using Microsoft.AspNetCore.Http;
  58. using Newtonsoft.Json;
  59. using System;
  60. using System.Collections.Generic;
  61. using System.Linq;
  62. using System.Text;
  63. using System.Threading.Tasks;
  64. namespace BlogPluginApi.Service
  65. {
  66.     public static class BlogService
  67.     {
  68.         public static string Query(Dictionary<string, string> queryParameters)
  69.         {
  70.             SearchDto searchBlogDto = new SearchDto();
  71.             if (queryParameters.TryGetValue("SearchName", out var searchName))
  72.             {
  73.                 searchBlogDto.SearchName = searchName;
  74.             }
  75.             var result = string.Join(",", DbHelper.Db.Queryable<Blog>().Select(s => s.Title).ToList());
  76.             return result;
  77.         }
  78.         public static string Delete(object param)
  79.         {
  80.             var strParam = param.ToString();
  81.             if (string.IsNullOrEmpty(strParam)) return string.Empty;
  82.             var deleteIds = JsonConvert.DeserializeObject<List<int>>(strParam);
  83.             if (deleteIds != null)
  84.             {
  85.                 var count = DbHelper.Db.Deleteable<Blog>().Where(x => deleteIds.Contains(x.Id)).ExecuteCommand();
  86.                 return count.ToString();
  87.             }
  88.             return string.Empty;
  89.         }
  90.         public static string Save(object param)
  91.         {
  92.             var strParam = param.ToString();
  93.             if (string.IsNullOrEmpty(strParam)) return string.Empty;
  94.             var inputDto = JsonConvert.DeserializeObject<Blog>(strParam);
  95.             if (inputDto != null)
  96.             {
  97.                 var count = DbHelper.Db.Insertable(inputDto).ExecuteCommand();
  98.             }
  99.             return "success";
  100.         }
  101.         public static string Update(object param)
  102.         {
  103.             var strParam = param.ToString();
  104.             if (string.IsNullOrEmpty(strParam)) return string.Empty;
  105.             var inputDto = JsonConvert.DeserializeObject<Blog>(strParam);
  106.             if (inputDto != null)
  107.             {
  108.                 var count = DbHelper.Db.Updateable(inputDto).ExecuteCommand();
  109.             }
  110.             return "success";
  111.         }
  112.         public static string UploadFile(Dictionary<string, string> queryParameters, IFormCollection formData)
  113.         {
  114.             string UploadId = string.Empty;
  115.             if (queryParameters.TryGetValue("uploadId", out var uploadId))
  116.             {
  117.                 UploadId = uploadId;
  118.             }
  119.             var file = formData.Files.FirstOrDefault();
  120.             if (file != null)
  121.                 return file.FileName;
  122.             return string.Empty;
  123.         }
  124.     }
  125. }
复制代码
 7,然后我们生成一下BlogPluginApi项目,把生成的dll文件放在放在主项目的Plugins文件夹下就可以了。
 留意:
系统必须被设计为可以或许识别和管理差异的插件版本,而且可以或许在运行时安全地切换这些版本。
结语

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

铁佛

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表