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

投简历的时候是不是有人问我有没有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. 【鸿蒙千帆起】高德地图携手HarmonyOS NEXT,开启智能出行新篇章

    2024年1月18日下午,华为举办了鸿蒙生态千帆启航仪式,对外宣布HarmonyOS NEXT星河预览版现已开放申请,同时,首批200+鸿蒙原生应用加速开发,鸿蒙生态设备数量更是突破了8亿大关.这些进 ...

  2. Hi3861编译烧录更快捷

     原文链接:https://mp.weixin.qq.com/s/TApbA6VUYUVWrGGaDyodbA,点击链接查看更多技术内容: HUAWEI DevEco Device Tool是华为面向 ...

  3. Java构建工具:Maven与Gradle的对比

    在Java码农的世界里,构建工具一直是一个不可或缺的元素.一开始,世上是只有一个构建工具的那就是Make后来发展为GNU Make.但是由于需求的不断涌现,这个小圈子里又逐渐衍生出其他千奇百怪的构建工 ...

  4. Launching Teamviewer remotely through SSH

    Launching Teamviewer remotely through SSH When you need to manage your Server remotely, but you can ...

  5. CentOS下修改 MySQL 的密码

    做服务器运维,修改 MySQL 的密码是经常的需要,定期修改 MySQL 密码是网站安全的一个保证.这里记录一下修改 MySQL 密码的一些命令,方便以后查看. 修改root密码 CentOS 下 M ...

  6. js es6 标签模板还原字符串

    前言 模板字符串的功能,它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串.这被称为"标签模板"功能(tagged template). 举个栗子: function ...

  7. nohup训练pytorch模型时的报错以及tmux的简单使用

    问题: 在使用nohup命令后台训练pytorch模型时,关闭ssh窗口,有时会遇到下面报错: WARNING:torch.distributed.elastic.agent.server.api:R ...

  8. ABP -Vnext框架一步一步入门落地教程——ABP Vnext框架代码安装和启动(一)

    兄弟们,人生需要指引,而复制是成功最快的方式,让我们开始行动吧 --codesoft 教程介绍 ABP-Vnext框架我们之前摸了无数次,好象初恋的女孩,一直在靠近,一直在努力,一直不敢盯着她的眼睛说 ...

  9. Java Agent 踩坑之 appendToSystemClassLoaderSearch 问题

    简介: 从 Java Agent 报错开始,到 JVM 原理,到 glibc 线程安全,再到 pthread tls,逐步探究 Java Agent 诡异报错. 作者:鲁严波   从 Java Age ...

  10. Serverless Devs 2.0 开箱测评:Serverless 开发最佳实践

    ​简介: 当下,Serverless 概念很火,很多同学被 Serverless 的优势吸引过来,比如它的弹性伸缩,免运维,高可用,资费少.但真正使用起来去落地的时候发现问题很多,大型项目如何组织函数 ...