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

投简历的时候是不是有人问我有没有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热加载 二的更多相关文章

  1. 最简破解-java代码热加载热部署IDEA插件JRebel

    如果经济实力允许的话,还是建议大家去购买收费版.支持原创作者,才能有更好的产品出现. 一.Jrebel插件介绍 JRebel一款帮助我们在开发过程中实现热加载的插件,目前来说,在IDEA中实现热加载最 ...

  2. IntelliJ IDEA 2017.3.2 热加载(Hot Swap)

    一.IntelliJ IDEA 自带热加载,修改代码后点击Ctrl + F9即可 缺点:1.Ctrl + F9只对当前类重新编译加载 2.只支持构造代码块的CRUD.方法体内代码修改.资源文件内容的修 ...

  3. Aspnetcore下面服务器热更新与配置热加载

    原文:Aspnetcore下面服务器热更新与配置热加载 Asp.net的热更新方案Appdomain在aspnetcore中不被支持了 新的方案如下: 配置文件更新选项 reloadOnChange ...

  4. Windows7 安装vs2015 之后 调试Web项目IIS启动不了 aspnetcore.dll未能加载

    安装windows企业版,整整折腾了两天了,一个本身家里网络环境不好,时不时掉线,终于披荆斩棘,克服了所有困难,结果VS2015 EnterPrise 版本在调试Web环境的时候,始终在任务栏里找不到 ...

  5. 模块 DLL C:\WINDOWS\system32\inetsrv\aspnetcore.dll 未能加载。返回的数据为错误信息。

    更新了win10的版本后,就启动原来的iis发布的程序 程序池就自动关闭.后来 启动网站 iis程序池自动关闭. 在为应用程序池“.NET v4.5”提供服务的工作进程“21908”中,协议“http ...

  6. C#开发奇技淫巧二:根据dll文件加载C++或者Delphi插件

    原文:C#开发奇技淫巧二:根据dll文件加载C++或者Delphi插件 这两天忙着把框架改为支持加载C++和Delphi的插件,来不及更新blog了.      原来的写的框架只支持c#插件,这个好做 ...

  7. webpack+vue2.0项目 (二)热加载,vue-router

    目录创建好之后,命令行输入 npm run dev 因为在配置文件config/index.js里: dev: { env: require('./dev.env'), port: 8080, aut ...

  8. dll的加载方式主要分为两大类,显式和隐式链接

    之前简单写过如何创建lib和dll文件及简单的使用(http://blog.csdn.net/betabin/article/details/7239200).现在先再深入点写写dll的加载方式. d ...

  9. 使用gulp实现文件压缩及浏览器热加载

    一.安装gulp 首先,你要安装过nodejs,如果没有安装过的同学请自行下载.  先再命令行里输入   npm install gulp -g   下载gulp 二.创建gulp项目 创建一个你需要 ...

  10. java的热部署和热加载

    ps:热部署和热加载其实是两个类似但不同的概念,之前理解不深,so,这篇文章重构了下. 一.热部署与热加载 在应用运行的时升级软件,无需重新启动的方式有两种,热部署和热加载. 对于Java应用程序来说 ...

随机推荐

  1. HDC 2022 开发者主题演讲与技术分论坛干货分享(附课件)

     原文:https://mp.weixin.qq.com/s/axm6HyX0PqKCKksFxIfehg,点击链接查看更多技术内容.   11月4日-11月6日,HDC 2022在东莞成功举办,这是 ...

  2. POJ2251 基础bfs

    题目: 你进入了一个3D的宝藏地宫中探寻宝藏到了宝藏,你可以找到走出地宫的路带出宝藏,或者使用炉石空手回家. 地宫由立方体单位构成,立方体中不定会充满岩石.向上下前后左右移动一个单位需要一分钟.你不能 ...

  3. lowdb 在electron 使用中注意的问题

    前言 可能很多人都没有听说过这个lowdb,但是它的确存在,而且在electron 中用到还是挺多的. 如何在electron 的render 进程中是引用electron 模块. 我们知道一个问题, ...

  4. Spark如何对源端数据做切分?

    简介: 典型的Spark作业读取位于OSS的Parquet外表时,源端的并发度(task/partition)如何确定?特别是在做TPCH测试时有一些疑问,如源端扫描文件的并发度是如何确定的?是否一个 ...

  5. 如何发起 MQTT 亿级连接和千万消息吞吐性能测试

    ​简介:MQTT 协议凭借简单易实现.支持 QoS.报文小等特点,占据了物联网协议的半壁江山. 作者:亦炎 随着 5G 时代的来临,万物互联的伟大构想正在成为现实.联网的物联网设备 在 2021 年已 ...

  6. EMR on ACK 全新发布,助力企业高效构建大数据平台

    ​简介: 阿里云 EMR on ACK 为用户提供了全新的构建大数据平台的方式,用户可以将开源大数据服务部署在阿里云容器服务(ACK)上.利用 ACK 在服务部署和对高性能可伸缩的容器应用管理的能力优 ...

  7. rerank来提升RAG的准确度的策略

    RAG(Retrieval-Augmented Generation)是一种结合检索和生成两种技术的模型,旨在通过检索大规模知识库来增强文本生成任务的准确性. 要通过reranking(重排序)来提升 ...

  8. [Linux] IP 地址配置, 网络地址配置文件

    # 查看与配置网络状态命令 $ ifconfig # 临时设置 eth0 网卡的 IP 地址与子网掩码,netmask 可以省略 $ ifconfig eth0 192.168.0.100 netma ...

  9. [FE] Quasar 变通 loading 单纯使用遮罩效果的方法

    Quasar 的 loading 组件是提供加载中的遮罩效果的. 如果你不想要 loading 的效果,只想保留遮罩效果,那么你可以通过 show() 方法的参数进行调整. 把 spinnerSize ...

  10. [K8s] Docker 单节点部署 Rancher

    Rancher 是通过 Web 界面管理 k8s 集群的工具,本身支持使用 Docker 启动. 单节点部署只需要 docker run 即可,易用性高,高可用部署可以使用 nginx 反向代理机制. ...