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应用程序来说 ...
随机推荐
- 驾考宝典携手HMS Core统一扫码服务,构建复杂场景中的流畅扫码体验
"驾考宝典"是一款颇具人气的互联网综合驾照考试学习应用,通过强大的驾考功能,在手机移动端为学车学员提供从报名.学习到拿本的全方位驾考服务.作为一个专业的驾培平台,"驾考宝 ...
- Qt线程简单使用二:QObject~创建任务类
需求: 点击QPushButton按钮,QLabel中的数字,不断累加,一直到999. 做法: 创建任务类,用来完成任务,创建子线程,将任务类放到子线程中,点击QPushButton后,先发送 ...
- Qt线程简单使用一:QThread~创建线程类子类
需求: 点击QPushButton按钮,QLabel中的数字,不断累加,一直到999. 做法: 点击QPushButton后,启动线程,线程while循环,不断发送累加的数字回主线程,修改QL ...
- centos 6.4更新163源
centos 6.4更新163源 1. 备份现在的源文件 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base. ...
- 【数学】主成分分析(PCA)的详细深度推导过程
Based on Deep Learning (2017, MIT) book. 本文基于Deep Learning (2017, MIT),推导过程补全了所涉及的知识及书中推导过程中跳跃和省略的部分 ...
- 龙湖千丁基于 ACK@Edge 的云原生智慧停车系统架构实践
简介: 结合龙湖千丁自研的新版停车云系统以及 ACK@Edge 提供的标准 Kubernetes 服务以及云边一体化协同解决方案,整体来着,边缘部署时间成本由 1 天缩短到 3 小时,将之前的手动升级 ...
- EMR StarRocks 极速数据湖分析原理解析
简介:数据湖概念日益火热,本文由阿里云开源大数据 OLAP 团队和 StarRocks 数据湖分析团队共同为大家介绍" StarRocks 极速数据湖分析 "背后的原理. [首月9 ...
- KubeDL HostNetwork:加速分布式训练通信效率
简介:ubeDL 为分布式训练作业带来了 HostNetwork 网络模式,支持计算节点之间通过宿主机网络相互通信以提升网络性能,同时适应 RDMA/SCC 等新型高性能数据中心架构的网络环境,此外 ...
- 3种方式自动化控制APP
自动化控制APP不管是在工作还是生活方面,都可以帮助我们高效地完成任务,节省时间和精力.本文主要介绍自动化控制APP的3种常用方式. 1.Python + adb 这种方式需要对Android有一些基 ...
- frp内网穿透器安装与介绍
1.NAT访问 2.FRP介绍 中文官方文档:https://gofrp.org/docs/ github:https://github.com/fatedier/frp/releases frp 采 ...