首页 > 编程语言 >aspnetcore插件开发dll热加载 二

aspnetcore插件开发dll热加载 二

时间:2024-05-25 17:03:08浏览次数:21  
标签:builder 插件 Name aspnetcore dll new var using public

这一篇文章应该是个总结。

投简历的时候是不是有人问我有没有abp的开发经历,汗颜!

在各位大神的尝试及自己的总结下,还是实现了业务和主机服务分离,通过dll动态的加载卸载,控制器动态的删除添加。

项目如下:

 

演示效果:

 

下面就是代码部分:

重点

1.IActionDescriptorChangeProvider接口,(关于添加删除可以通过后台任务检测刷新,移除控制器操作)

2.builder.Services.AddControllers().ConfigureApplicationPartManager和AssemblyLoadContext搭配加载业务的dll(动态链接库)。

我的业务代码很简单,可能有人要说了,那复杂的业务,有很多业务类,注入这块怎么办,怎么实现整个的调用链。

关于业务和主服务之间的关联代码就在这了

namespace ModuleLib
{
    //可以给个抽象类,默认实现。否则各个服务每次实现接口会多做一步删除为实现接口的动作
    public interface IModule
    {
        void ConfigureService(IServiceCollection services, IConfiguration configuration=null);
        void Configure(IApplicationBuilder app, IWebHostEnvironment env = null);
    } 
}

 

看下面的项目,有没有一点模块化开发的感觉,但是这次分离的很彻底,只需要dll就行,不需要程序集引用。

{
  "Modules": [
    {
      "id": "FirstWeb",
      "version": "1.0.0",
      "path": "C:\\Users\\victor.liu\\Documents\\GitHub\\AspNetCoreSimpleAop\\LastModule\\FirstWeb\\bin\\Debug\\net8.0"
    },
    {
      "id": "SecondService",
      "version": "1.0.0",
      "path": "C:\\Users\\victor.liu\\Documents\\GitHub\\AspNetCoreSimpleAop\\LastModule\\SecondService\\bin\\Debug\\net8.0"   //����csproj�ļ�����ָ�����з������ɵ�ָ����һ��Ŀ¼���������
    }
  ]
}

以Assembly为单位做存储

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Common
{
    public class ModuleInfo
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public Version Version { get; set; }
        public string Path { get; set; } = "lib";
        public Assembly Assembly { get; set; }
    }
}

在初次加载的时候注入Imodule,并且缓存起来,这样避免了反射的操作,之前的做法是通过反射来拿IModule

using Common;
using ModuleLib;
using System.Reflection;

namespace MainHost.ServiceExtensions
{
    public static class InitModuleExt
    {
        public static void InitModule(this IServiceCollection services,IConfiguration configuration)
        {
            var modules = configuration.GetSection("Modules").Get<List<ModuleInfo>>();
            foreach (var module in modules)
            {
                GolbalConfiguration.Modules.Add(module);
                module.Assembly = Assembly.LoadFrom($"{module.Path}\\{module.Id}.dll"); //测试才这么写

                var moduleType = module.Assembly.GetTypes().FirstOrDefault(t => typeof(IModule).IsAssignableFrom(t));
                if ((moduleType != null) && (moduleType != typeof(IModule)))
                {
                    services.AddSingleton(typeof(IModule), moduleType);
                }
            }
        }
    }
}

 

再看看Program是怎么写的,等等,为什么注释掉了重要的代码呢

using BigHost;
using BigHost.AssemblyExtensions;
using Common;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Configuration;
using ModuleLib;
using System.Xml.Linq;
using DependencyInjectionAttribute;

var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
builder.Configuration.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("appsettings.Modules.json", optional: false, reloadOnChange: true);
//builder.Services.InitModule(builder.Configuration);
//var sp = builder.Services.BuildServiceProvider();
//var modules = sp.GetServices<IModule>();
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

//最新dotnet没有这些
builder.Services.AddControllers().ConfigureApplicationPartManager(apm =>
{
    var context = new CollectibleAssemblyLoadContext();
    DirectoryInfo DirInfo = new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), "lib"));
    foreach (var file in DirInfo.GetFiles("*.dll"))
    {
        //if(!(file.Name.Contains("Test001Controller") || file.Name.Contains("Test002Controller")))
        //{
        //    continue;
        //}//不能屏蔽掉依赖引用
        var assembly = context.LoadFromAssemblyPath(file.FullName);
        var controllerAssemblyPart = new AssemblyPart(assembly);
        apm.ApplicationParts.Add(controllerAssemblyPart);
        ExternalContexts.Add(file.Name, context);
    }
});
    //builder.Services.AddTransient<IProductBusiness, ProductBusiness>();
    //foreach (var module in modules)
    //{
    //    module.ConfigureService(builder.Services, builder.Configuration);
    //}
    //GolbalConfiguration.Modules.Select(x => x.Assembly).ToList().ForEach(x =>
    //{
    //    builder.Services.ReisterServiceFromAssembly(x);
    //    var controllerAssemblyPart = new AssemblyPart(x);
    //    apm.ApplicationParts.Add(controllerAssemblyPart);
    //    ExternalContexts.Add(x.GetName().Name, context);
    //});
//});
//GolbalConfiguration.Modules.Select(x => x.Assembly).ToList().ForEach(x => builder.Services.ReisterServiceFromAssembly(x));
builder.Services.AddSingleton<IActionDescriptorChangeProvider>(ActionDescriptorChangeProvider.Instance);
builder.Services.AddSingleton(ActionDescriptorChangeProvider.Instance);



var app = builder.Build();
ServiceLocator.Instance = app.Services;
//foreach (var module in modules)
//{
//    module.Configure(app, app.Environment);
//}

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseHttpsRedirection();


app.MapGet("/Add", ([FromServices] ApplicationPartManager _partManager, string name) =>
{

    FileInfo FileInfo = new FileInfo(Path.Combine(Directory.GetCurrentDirectory(), "lib/" + name + ".dll"));
    using (FileStream fs = new FileStream(FileInfo.FullName, FileMode.Open))
    {
        var context = new CollectibleAssemblyLoadContext();
        var assembly = context.LoadFromStream(fs);
        var controllerAssemblyPart = new AssemblyPart(assembly);

        _partManager.ApplicationParts.Add(controllerAssemblyPart);

        //ExternalContexts.Add(name + ".dll", context);
        ExternalContexts.Add(name, context);

        //更新Controllers
        ActionDescriptorChangeProvider.Instance.HasChanged = true;
        ActionDescriptorChangeProvider.Instance.TokenSource!.Cancel();
    }
    return "添加{name}controller成功";
})
.WithTags("Main")
.WithOpenApi();

app.MapGet("/Remove", ([FromServices] ApplicationPartManager _partManager, string name) =>
{
    //if (ExternalContexts.Any(
    //    $"{name}.dll"))
    if (ExternalContexts.Any(
   $"{name}"))
    {
        var matcheditem = _partManager.ApplicationParts.FirstOrDefault(x => x.Name == name);
        if (matcheditem != null)
        {
            _partManager.ApplicationParts.Remove(matcheditem);
            matcheditem = null;
        }
        ActionDescriptorChangeProvider.Instance.HasChanged = true;
        ActionDescriptorChangeProvider.Instance.TokenSource!.Cancel();
        //ExternalContexts.Remove(name + ".dll");
        ExternalContexts.Remove(name);
        return $"成功移除{name}controller";
    }
    else
    {
        return "$没有{name}controller";
    }
});
app.UseRouting(); //最新dotnet没有这些
app.MapControllers();  //最新dotnet没有这些
app.Run();

 

这里先对上面的尝试做个总结:

模块化开发通过IModule分离各个模块解耦,通过dll把接口加入到主程序,很nice,但是,我还想更深入一层,把这个接口也一并做成可拔可插,这样就不得不考虑如何动态的重载controller,这也没问题。重中之重来了,上面的都做到了,但是我要的不仅仅是增加删除一个controller,关联的业务代码发生了改变如何重载刷新,依赖注入这一块绕不过去。并没有好的解决办法,就这样项目戛然而止。

目前有两种解决办法:

1.加个中间层,通过反射去动态获取业务实现

2.业务实现通过new对象来拿。

下面是代码:

using IOrder.Repository;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
using System.Runtime.Loader;

namespace AutofacRegister
{
    public interface IRepositoryProvider
    {
        IRepository GetRepository(string serviceeName);
    }
    public class RepositoryProvider : IRepositoryProvider
    {
        private readonly Dictionary<string, (Assembly assembly, DateTime lastModified)> _assemblyCache = new Dictionary<string, (Assembly assembly, DateTime lastModified)>();
        private readonly Dictionary<string, IRepository> _typeCache = new Dictionary<string, IRepository>();

        public IRepository GetRepository(string serviceName)
        {
            var path = $"{Directory.GetCurrentDirectory()}\\lib\\{serviceName}.Repository.dll";
            var lastModified = File.GetLastWriteTimeUtc(path);
            if (_assemblyCache.TryGetValue(path, out var cachedEntry) && cachedEntry.lastModified == lastModified)
            {
                // 使用缓存中的 Assembly 对象
                return CreateInstanceFromAssembly(cachedEntry.assembly,serviceName);
            }
            else
            {
                // 加载并缓存新的 Assembly 对象
                var assembly = LoadAssemblyFromFile(path);
                _assemblyCache[path] = (assembly, lastModified);
                return CreateInstanceFromAssembly(assembly,serviceName);
            }
        }

        private Assembly LoadAssemblyFromFile(string path)
        {
            var _AssemblyLoadContext = new AssemblyLoadContext(Guid.NewGuid().ToString("N"), true);
            using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read))
            {
                return _AssemblyLoadContext.LoadFromStream(fs);
            }
        }
        private IRepository CreateInstanceFromAssembly(Assembly assembly,string serviceName)
        {
            var  type_key = $"{assembly.FullName}_{serviceName}";
            if(_typeCache.TryGetValue(type_key, out var cachedType))
            {
                return _typeCache[type_key];
            }
            var type = assembly.GetTypes()
                .Where(t => typeof(IRepository).IsAssignableFrom(t) && !t.IsInterface)
                .FirstOrDefault();

            if (type != null)
            {
                var instance= (IRepository)Activator.CreateInstance(type);
                _typeCache[type_key] = instance;
                return instance;
            }
            else
            {
                throw new InvalidOperationException("No suitable type found in the assembly.");
            }
        }
    }
}

 

所有的注入业务放到单独的注入文件中,

using Autofac;
using IOrder.Repository;
using Order.Repository;

namespace AutofacRegister
{
    public class RepositoryModule:Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            //builder.RegisterType<Repository>().As<IRepository>().SingleInstance();
            builder.RegisterType<RepositoryProvider>().As<IRepositoryProvider>().InstancePerLifetimeScope();
        }
    }
}

 

上面的代码可以再加一层代理,类似这样

using CustomAttribute;
using System.Reflection;
using ZURU_ERP.Base.Common.UnitOfWork;
using ZURU_ERP.Base.Common;
using ZURU_ERP.Base.Model;
using System.Collections.Concurrent;

namespace ZURU_ERP.Base.Reflect
{
    public class MethodInfoCache
    {
        public string Name { get; set; }
        public Type ClassType { get; set; }
        public CusTransAttribute TransAttribute { get; set; }

        public List<CusActionAttribute> ActionAttributes { get; set; }
        public bool UseTrans => (TransAttribute == null);
        public bool UseAop => ActionAttributes.Any();
    }
    public class CusProxyGenerator<T> : DispatchProxy where T:class
    {

        private readonly ConcurrentDictionary<string, MethodInfoCache> _cache = new ConcurrentDictionary<string, MethodInfoCache>();
        private IBusiness<T> business;
        private  List<ICusAop> cusAop;

        protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
        {
            #region 缓存优化 未经过测试
            string methodKey = targetMethod.Name;
            if (!_cache.ContainsKey(methodKey))
            {
                var classType = business.GetType();
                var transAttribute = classType.GetMethod(targetMethod.Name).GetCustomAttributes<CusTransAttribute>().FirstOrDefault();
                var actionAttributes = classType.GetMethod(targetMethod.Name).GetCustomAttributes<CusActionAttribute>().ToList();
                _cache[methodKey] = new MethodInfoCache()
                {
                    Name = methodKey,
                    ClassType = classType,
                    TransAttribute = transAttribute,
                    ActionAttributes = actionAttributes
                };
            }
            var methodInfoCache = _cache[methodKey];
            object result;
            if (methodInfoCache.UseAop)
            {
                var actionnames = methodInfoCache.ActionAttributes.Select(x => x.Name).ToList();
                var waitInvokes = cusAop.Where(x => actionnames.Contains(x.GetType().Name)).OrderBy(x => actionnames.IndexOf(x.GetType().Name)).ToList(); //排序
                foreach (var item in waitInvokes)
                {
                    item.Before(args);
                }

                result = methodInfoCache.UseTrans ? Trans(targetMethod, args, out result) : targetMethod.Invoke(business, args);
                foreach (var item in waitInvokes)
                {
                    item.After(new object[] { result });
                }
                return result;
            }
            else
            {
                return methodInfoCache.UseTrans ? Trans(targetMethod, args, out result) : targetMethod.Invoke(business, args);
            } 
            #endregion

            #region 没缓存原代码 经过测试

            //bool useTran = false;
            //var classType = business.GetType();
            //var useClassTrans = classType.GetCustomAttributes<CusTransAttribute>();
            //if (useClassTrans.Any())
            //{
            //    useTran = true;
            //}
            //else
            //{
            //    useTran = classType.GetMethod(targetMethod.Name).GetCustomAttributes<CusTransAttribute>().Any();  //是否使用事务
            //}

            //var actionnames = classType.GetCustomAttributes<CusActionAttribute>().Select(x => x.Name).ToList();

            //var waitInvokes = cusAop.Where(x => actionnames.Contains(x.GetType().Name)).OrderBy(x => actionnames.IndexOf(x.GetType().Name)).ToList(); //排序

            //foreach (var item in waitInvokes)
            //{
            //    item.Before(args);
            //}

            //object result;
            //if (useTran)
            //{
            //    return Trans(targetMethod, args, out result);
            //}
            //else
            //{
            //    result = targetMethod.Invoke(business, args);
            //}

            //foreach (var item in waitInvokes)
            //{
            //    item.After(new object[] { result });
            //}

            //return result;
            #endregion
        }

        private object? Trans(MethodInfo? targetMethod, object?[]? args, out object result)
        {
            var _unitOfWorkManage = App.GetService<IUnitOfWorkManage>();

            Console.WriteLine($"{targetMethod.Name} transaction started.");

            try
            {
                if (_unitOfWorkManage.TranCount <= 0)
                {
                    Console.WriteLine($"Begin Transaction");
                    _unitOfWorkManage.BeginTran();
                }
                result = targetMethod.Invoke(business, args);
                if (result is ApiResult apiResult && !apiResult.success)
                {
                    Console.WriteLine("apiResult return false Transaction rollback.");
                    _unitOfWorkManage.RollbackTran();
                    return apiResult;
                }
                if (_unitOfWorkManage.TranCount > 0)
                    _unitOfWorkManage.CommitTran();
                Console.WriteLine("Transaction Commit.");
                Console.WriteLine($"{targetMethod.Name}  transaction succeeded.");

                return result;
            }
            catch (Exception e)
            {
                _unitOfWorkManage.RollbackTran();
                Console.WriteLine("Transaction Rollback.");
                Console.WriteLine($"{targetMethod.Name}  transaction failed: " + e.Message);
                throw;
            }
        }

        public static IBusiness<T> Create(IBusiness<T> business, List<ICusAop> cusAop)
        {
            object proxy = Create<IBusiness<T>, CusProxyGenerator<T>>();
            ((CusProxyGenerator<T>)proxy).SetParameters(business, cusAop);
            return (IBusiness<T>)proxy;
        }

        private void SetParameters(IBusiness<T> business, List<ICusAop> cusAop)
        {
            this.business = business;
            this.cusAop = cusAop;
        }
    }
}

 

由于这层代码没有走依赖注入,想用各种aop组件,灵活性稍微低了一点点。

下面第二种直接在业务代码中new对象也不是不可,这一层的前后需要的都可以注入到容器里面去。只不过这一层就想到包装类一层不要在使用这个类的时候做过多的职责承担

using IBusiness;

namespace Business
{
    public class ProductBusiness : IDisposable// : IProductBusiness
    {
        public static readonly ProductBusiness Instance;
        private bool _disposed = false; 
        static ProductBusiness()
        {
            Instance = new ProductBusiness();
        }
      
        private ProductBusiness()
        {
            // 初始化资源
        }
        public async Task<int> AddProduct(string name, decimal price)
        {
            await Task.CompletedTask;
            return 1;
        }

        // 实现IDisposable接口
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;

            if (disposing)
            {
                // 释放托管资源
            }

            // 释放非托管资源
            _disposed = true;
        }

        // 析构函数
        ~ProductBusiness()
        {
            Dispose(false);
        }
    }
}

使用的时候就直接拿实例:

  [HttpPost]
  public async Task<int> Add()
  {
      //using var scope = ServiceLocator.Instance.CreateScope();
      //var business = scope.ServiceProvider.GetRequiredService<IProductBusiness>();
      using var business = ProductBusiness.Instance;
      return await business.AddProduct("product1",12.1m);
  }

 

demo源代码:

liuzhixin405/AspNetCoreSimpleAop (github.com)

 

标签:builder,插件,Name,aspnetcore,dll,new,var,using,public
From: https://www.cnblogs.com/morec/p/18211276

相关文章

  • Android插件化的一种方案
    思路:打开插件包里边的activity时统一都用一个ProxyActivity作为代理  1、首先宿主APP提供ProxyActivity,当宿主需要打开插件包中的Activity时,一律是启动的ProxyActivity,在启动ProxyActivity的intent中携带我们真正需要去打开的插件包中Activity的类全名。publicclassPro......
  • Python中动态调用C#的dll动态链接库中方法
    在Python中调用C#的dll库_哔哩哔哩_bilibili 环境准备: 安装pythonnetpipinstallpythonnet 在Python中调用C#动态链接库(DLL),可以使用pythonnet库,它允许直接使用.NET的程序集。以下是一个示例,展示如何使用pythonnet调用C#动态链接库中的方法。【pythonnet详解】—......
  • 视频号直播间自动回复评论插件-循环发送话术-视频号直播机器人
    在使用视频号直播的时候,我们可以登录到视频号直播中控台,利用我开发的插件实现自动回复评论和循环发送评论原理:MutationObserver机制MouseEvent事件和dispatchEvent使用方法:开启直播后,登录到视频号直播后台https://channels.weixin.qq.com/platform/live/home开启插件:点击......
  • Mybatis-plus插件功能
    MyBatisPlus自带分页插件,只需简单的配置就可以实现分页功能添加配置类注解@Configuration@ConfigurationpublicclassMyBatisPlusConfig{//配置分页插件@BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptorin......
  • 网页视频下载插件FetchV,支持Chrome/Edge/FireFox浏览器
    FetchV是一款专为现代浏览器用户设计的高性能视频下载工具,全面兼容Chrome、Edge及其他基于Chromium内核的浏览器。其特点如下:全能视频下载解决方案:轻松下载包括HLS(m3u8)在内的多种流媒体格式、MP4、WEBM、FLV等静态网页视频及音频文件(如MP3),满足多元化下载需求。流媒体直下......
  • [JarEditor]一款无需解压直接编辑修改jar内容的IDEA插件
    前言IDEA自带的反编译插件能打开jar反编译class,遗憾的是不能编辑class,有的时候我们需要修改jar的时候还需要解压,反编译class,然后新建.java,修改代码,再编译,最后打包jar。我敢说中间的过程你肯定不是一气呵成的,如果jar有依赖还要花时间去排查,打包的时候还需要注意编译jdk的版本,有的......
  • 画图工具之PlantUML插件使用
    目录1PlantUML插件1.1引言1.2什么是PlantUML1.3PlantUML插件1.3.1IntelliJIDEA中插件1.3.2VSCode中插件1.3.3使用例子1.4PlantUML时序图语法1.4.1声明参与者1.4.2消息传递1.4.2.1同步消息1.4.2.2异步消息1.4.2.3返回消息1.4.2.4自调用1.4.3生命线(Lifeline)与激活......
  • 爆火AI美女跳舞制作全流程-SD插件Ebsynth_Utility(附带所有工具包)
    1.基础介绍AIGC|ChatGPT行业介绍1.1SD简介StableDiffusionXL能够生成几乎任何艺术风格的高质量图像,是用来生成写实图像的最佳开放模型。StableDiffusion是一个可以和MJ相媲美的AI出图工具,简称SD它是一个开源的、免费的项目,没有公司在经营,如果你想用,是需要安装到自......
  • 【AI虚拟试穿】革新购物体验,一键换衣/试衣浏览器插件震撼上线
    用户友好型AI试衣,重塑在线购物新纪元1.产品简介:HeyBeauty,一个前沿的AI平台,打破传统试衣界限,通过先进的AI技术,为用户带来前所未有的虚拟试衣体验。无需线下繁琐的试穿流程,即可在线享受与实体店媲美的购物乐趣。2.功能亮点:智能虚拟试穿:实时适应各种体型和尺寸,以超高真实......
  • mingw 编译生成的dll 如何在vs中使用
    1.mingw编译生成dll gcc-shared-olibtest.dll-Wl,--output-def,libtest.def,--out-implib,dlltest.a xxx.oxxx.o  有2个文件是我们需要的2.vs使用lib.exe将XXX.def文件(函数定义文件)生成为.lib导入库(1)打开VS工具-》命令行-》powershell(想自己去VS安装目录下找......