首页 > 编程语言 >C# WebAPI 插件热插拔

C# WebAPI 插件热插拔

时间:2025-01-23 13:55:15浏览次数:1  
标签:WebAPI 插件 string C# System var using public

背景

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应用和服务的一个亮点。

标签:WebAPI,插件,string,C#,System,var,using,public
From: https://www.cnblogs.com/BFMC/p/18687474

相关文章

  • Android平台从上到下,无需ROOT/解锁/刷机,应用级拦截框架的最后一环,SVC系统调用拦截。
    Android平台从上到下,无需ROOT/解锁/刷机,应用级拦截框架的最后一环——SVC系统调用拦截。☞Github:https://www.github.com/iofomo/abyss☜由于我们虚拟化产品的需求,需要支持在普通的Android手机运行。我们需要搭建覆盖应用从上到下各层的应用级拦截框架,而Abyss作为系......
  • docker:连接与登录私库、拉取镜像、上传镜像、保存容器为镜像、保存镜像为文件、读取文
    连接私库与修改dockerengine配置修改配置文件DockerEngine的配置通常存储在以下文件中:Linux系统:/etc/docker/daemon.jsonWindows系统:C:\ProgramData\docker\config\daemon.jsonmac系统:在dockerdesktop配置dockerengine中配置{"builder":{"gc":{......
  • ChatGPT高效处理图片技巧使用详解
    ChatGPT,作为OpenAI开发的预训练语言模型,主要用于生成自然语言文本的任务。然而,通过一些技巧和策略,我们可以将ChatGPT与图像处理模型结合,实现一定程度上的图像优化和处理。本文将详细介绍如何使用ChatGPT高效处理图片,内容精炼,层次分明,让读者在阅读后有所收获。一、ChatGPT与......
  • 打卡信奥刷题(651)用C++信奥P8396[普及组/提高] [CCC2022 S2] Good Groups
    [CCC2022S2]GoodGroups题目背景请注意:这道题是CCO2022J4GoodGroups的加强版。管理备注:似乎没有加强。题目描述一个班级会被分成ggg个组,每个组有三个人,这......
  • 源码分析之Openlayers样式篇IconImage类
    访问Openlayers网站(https://jinuss.github.io/Openlayers_map_pages/,网站是基于Vue3+Openlayers,里面有大量的实践和案例。觉得还不错,可以给个小星星Star,鼓励一波https://github.com/Jinuss/OpenlayersMap哦~概述在Openlayers中,IconImage类主要用于表示一个图标图......
  • HTTPS 证书自动化运维:使用Certbot来申请https证书实践指南
    简介在第一篇文章中,我们介绍了HTTPS证书的基础知识和自动化运维的重要性。本篇文章将进一步深入,提供具体的实践指南和案例分析,帮助您在实际操作中更有效地管理HTTPS证书,采用使用最广泛的证书Let’sEncrypt,实现自动化运维的最佳效果。一、HTTPS证书自动化运维实践指南1.......
  • 阿里开源语音克隆CosyVoice2 整合包
       CosyVoice2win整合包语音克隆CosyVoice2链接:https://pan.quark.cn/s/5e75615a5cd4修改webui.py默认值:#修改默认推理模式mode_checkbox_group=gr.Radio(choices=inference_mode_list,label='选择推理模式',value=inference_mode_list[1]) #修改随机推理种......
  • KeyClicker 为用户带来真实键盘打字声音体验的应用,再现机械键盘与打字机的打字感受
    如果你是一名作家,或者对打字机的声音情有独钟,KeyClicker将是你的理想选择。许多作家认为,打字机的声音能让他们更专注、更有创作灵感。虽然实体打字机的魅力独特,但它缺乏现代设备的便捷功能,例如高效的编辑与数字化操作。而使用KeyClicker,你既能享受打字机的经典声音,又能保留......
  • docker-py:在Python中轻松使用Docker引擎API,更加灵活地管理和使用容器性
    Docker是一种流行的容器技术,让开发者能够在各种环境中快速地构建、部署和管理应用程序。而docker-py是一个强大的Python库,可以让你通过Python代码与Docker引擎API进行互动,实现与Docker命令相同的功能。本文将详细介绍docker-py的安装、使用以及一些常见的操作示例,帮助你更好地利用......
  • leetcode——缺失的第一个整数(java)
    给你一个未排序的整数数组nums,请你找出其中没有出现的最小的正整数。请你实现时间复杂度为O(n)并且只使用常数级别额外空间的解决方案。示例1:输入:nums=[1,2,0]输出:3解释:范围[1,2]中的数字都在数组中。示例2:输入:nums=[3,4,-1,1]输出:2解释:1在数组中,但2......