aspnetcore插件开发dll热加载 二
这一篇文章应该是个总结。
投简历的时候是不是有人问我有没有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)
aspnetcore插件开发dll热加载 二的更多相关文章
- 最简破解-java代码热加载热部署IDEA插件JRebel
如果经济实力允许的话,还是建议大家去购买收费版.支持原创作者,才能有更好的产品出现. 一.Jrebel插件介绍 JRebel一款帮助我们在开发过程中实现热加载的插件,目前来说,在IDEA中实现热加载最 ...
- IntelliJ IDEA 2017.3.2 热加载(Hot Swap)
一.IntelliJ IDEA 自带热加载,修改代码后点击Ctrl + F9即可 缺点:1.Ctrl + F9只对当前类重新编译加载 2.只支持构造代码块的CRUD.方法体内代码修改.资源文件内容的修 ...
- Aspnetcore下面服务器热更新与配置热加载
原文:Aspnetcore下面服务器热更新与配置热加载 Asp.net的热更新方案Appdomain在aspnetcore中不被支持了 新的方案如下: 配置文件更新选项 reloadOnChange ...
- Windows7 安装vs2015 之后 调试Web项目IIS启动不了 aspnetcore.dll未能加载
安装windows企业版,整整折腾了两天了,一个本身家里网络环境不好,时不时掉线,终于披荆斩棘,克服了所有困难,结果VS2015 EnterPrise 版本在调试Web环境的时候,始终在任务栏里找不到 ...
- 模块 DLL C:\WINDOWS\system32\inetsrv\aspnetcore.dll 未能加载。返回的数据为错误信息。
更新了win10的版本后,就启动原来的iis发布的程序 程序池就自动关闭.后来 启动网站 iis程序池自动关闭. 在为应用程序池“.NET v4.5”提供服务的工作进程“21908”中,协议“http ...
- C#开发奇技淫巧二:根据dll文件加载C++或者Delphi插件
原文:C#开发奇技淫巧二:根据dll文件加载C++或者Delphi插件 这两天忙着把框架改为支持加载C++和Delphi的插件,来不及更新blog了. 原来的写的框架只支持c#插件,这个好做 ...
- webpack+vue2.0项目 (二)热加载,vue-router
目录创建好之后,命令行输入 npm run dev 因为在配置文件config/index.js里: dev: { env: require('./dev.env'), port: 8080, aut ...
- dll的加载方式主要分为两大类,显式和隐式链接
之前简单写过如何创建lib和dll文件及简单的使用(http://blog.csdn.net/betabin/article/details/7239200).现在先再深入点写写dll的加载方式. d ...
- 使用gulp实现文件压缩及浏览器热加载
一.安装gulp 首先,你要安装过nodejs,如果没有安装过的同学请自行下载. 先再命令行里输入 npm install gulp -g 下载gulp 二.创建gulp项目 创建一个你需要 ...
- java的热部署和热加载
ps:热部署和热加载其实是两个类似但不同的概念,之前理解不深,so,这篇文章重构了下. 一.热部署与热加载 在应用运行的时升级软件,无需重新启动的方式有两种,热部署和热加载. 对于Java应用程序来说 ...
随机推荐
- Ez_pycode_dis qsnctfwp
Python字节码基础 下载相关文件并打开,其中为 Python 字节码. 字节码格式为 源码行号 | 指令在函数中的偏移 | 指令符号 | 指令参数 | 实际参数值 根据上述字节码格式以及文件内容开 ...
- labelme转coco数据集
原始labelme数据目录结构如下: |-- images | |--- 1.jpg | |--- 1.json | |--- 2.jpg | |--- 2.json | |--- ....... | ...
- 接口API用例自动转locust测试用例
做接口测试是必要的,写完接口测试用例,再写locust压测脚本,其实差异不大: 写个简单的py,把接口测试脚本转为locust压测脚本,本例只是简单的示范: 原接口校验脚本: 1 # -*- codi ...
- https http2 http3
HTTP 1.1 对比 1.0,HTTP 1.1 主要区别主要体现在: 缓存处理:在 HTTP 1.0 中主要使用 header 里的 If-Modified-Since,Expires 来做为缓存判 ...
- nethttp和gin 路由
net/http 路由注册 func test1() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Requ ...
- java应用提速(速度与激情)
简介: 本文将阐述通过基础设施与工具的改进,实现从构建到启动全方面大幅提速的实践和理论. 作者 | 阿里巴巴CTO技术来源 | 阿里开发者公众号 联合作者:道延 微波 沈陵 梁希 大熊 断岭 北纬 未 ...
- Oracle数据到MaxCompute乱码问题详解
简介:集成Oracle数据到MaxCompute,乱码问题分析: 为什么,在oracle数据不乱码,集成到MaxCompute就乱码了? 问题在哪里? 1.1 乱码现象 DataWorks的数据离线 ...
- Flink on Zeppelin 流计算处理最佳实践
简介: 欢迎钉钉扫描文章底部二维码进入 EMR Studio 用户交流群 直接和讲师交流讨论~ 点击以下链接直接观看直播回放:https://developer.aliyun.com/live/247 ...
- LlamaIndex 是什么
LlamaIndex 是一个基于 LLM(大语言模型)的应用程序数据框架,适用于受益于上下文增强的场景. 这类 LLM 系统被称为 RAG(检索增强生成)系统. LlamaIndex 提供了必要的抽象 ...
- [GPT] nodejs 有哪些类似 jquery 语法的 html 解析库
在Node.js中,有一些类似jQuery语法的HTML解析库可供选择. 以下是其中几个常用的库: 1. Cheerio: Cheerio是一个快速.灵活且易于使用的HTML解析库,它提供了类似于 ...