两年,1444 次提交,我给自己撸了一套 .NET10 模块化框架(XiHan.Framework)

简介: XiHan.Framework 是一个基于 .NET 10 的轻量级、模块化后端框架,强调“原生优先、完全可控”。它通过拓扑排序模块加载、动态 API 自动生成、声明式事务/缓存/多租户等能力,大幅减少胶水代码与重复配置。已真实应用于中后台系统,开源可学、可改、可信赖。

如果你写过几年 .NET 后端,下面这些事大概率也折磨过你:

  • 每开一个新项目,DI 注册、中间件顺序、日志、事务、多租户、缓存这些横切的东西又要重新接一遍,接错顺序还很难查;
  • Controller 里全是"把参数原样丢给 Service"的胶水代码,写一遍 ApiResponse 包装、写一遍异常转状态码、写一遍 TraceId;
  • 想用现成的框架,ABP 很全但很重,想吃透它、想改它,成本不低;Furion 很顺手,但你总有些地方想按自己的想法来。

我也一样。ABP 是我的启蒙,Furion 给过我不少灵感,但折腾到最后,我还是想要一套每一行都是我自己写的、能完全吃透、优先用 .NET 原生、依赖能自己掌控的底座。于是从 2024 年开始,断断续续两年、1444 次提交,写成了现在这套 —— XiHan.Framework,一个基于 .NET 10 的模块化后端框架。

这篇不聊虚的。我直接带你看代码里真实长什么样。

它是什么

一句话:一套 .NET 10 的模块化后端框架framework/src 下现在是 57 个项目,全部以 NuGet 包发布,包名和项目名一一对应。

设计上有几条我一直在守的原则:优先用 .NET 内置能力(DI、HybridCacheSystem.Text.Json、内置限流器),能不引第三方就不引;模块之间用 [DependsOn] 声明依赖、拓扑排序自动加载;应用服务打个特性就自动变 API。

它也不是 demo —— 我那套多租户中后台 XiHan.BasicApp 就跑在它上面,是真刀真枪在用的。

几个我自己最满意的设计

一、整个框架的骨架,就是那套模块系统

每个模块继承 XiHanModule,用 [DependsOn] 声明它依赖谁,剩下的交给框架:

[DependsOn(
    typeof(XiHanWebApiModule),
    typeof(XiHanDataModule)
)]
public class MyAppModule : XiHanModule
{
   
    public override Task ConfigureServicesAsync(ServiceConfigurationContext context)
    {
   
        // 注册你自己的服务
        return Task.CompletedTask;
    }
}

启动时框架会把所有模块拓扑排序,保证被依赖的先加载。有意思的是,这个排序用的 SortByDependencies,就来自框架自己那个零依赖工具库 XiHan.Framework.Utils——底层工具反哺上层框架,闭环了。

每个模块提供 7 对(同步 + 异步)生命周期钩子,分两个阶段执行:

服务注册阶段                     应用初始化阶段
PreConfigureServices            OnPreApplicationInitialization
ConfigureServices          →    OnApplicationInitialization
PostConfigureServices           OnPostApplicationInitialization
                                OnApplicationShutdown

服务的注册也不用手写一堆 AddScoped。类只要实现 ITransientDependency / IScopedDependency / ISingletonDependency 这几个标记接口,框架扫描程序集时就按对应生命周期自动注册了。这套是 ABP 的经典玩法,我照着实现了一遍,因为确实好用。

二、写应用服务 = 写接口,没有 Controller

这是我最不想再重复劳动的地方。应用服务写好,就是 API,零 Controller:

public class UserAppService : IUserAppService  // 打上 [DynamicApi] 约定
{
   
    public Task<UserDto> GetUserAsync(long id) {
    ... }      // → GET  /api/.../user/{id}
    public Task<UserDto> CreateUserAsync(CreateUserDto input) {
    ... } // → POST /api/.../user
    public Task UpdateUserAsync(UpdateUserDto input) {
    ... } // → PUT  /api/.../user
}

HTTP 动词是从方法名前缀推出来的——Get/Create/Update/Delete 分别映射 GET/POST/PUT/DELETEAsync 后缀和动词前缀自动从路由里剥掉。这套映射表就写在 DynamicApiConventionOptions 里,你也能改。

但它不是靠反射一层层转发。框架在启动时用 IApplicationFeatureProvider<ControllerFeature> 扫出所有应用服务,然后用 IL 直接发射一个控制器类型出来,方法体就是"加载服务实例 → 压参数 → callvirt → ret"这么几条指令:

// DynamicApiControllerFactory.cs
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, serviceField);
for (var i = 0; i < parameters.Length; i++)
    il.Emit(OpCodes.Ldarg, i + 1);
il.Emit(OpCodes.Callvirt, serviceMethod);
il.Emit(OpCodes.Ret);

也就是说运行期它就是个正常的 MVC 控制器,性能贴近手写,配套的 Scalar / Swagger 文档也自动生成。

三、一个 [UnitOfWork],管住事务 + 审计 + 事件

业务方法打上 [UnitOfWork](或者干脆走中间件预留的 UoW),事务边界就交给 AOP 拦截器了:非 Get 方法默认开事务,Get 默认不开,异常自动回滚,全程不侵入业务代码。

真正让我满意的是 CompleteAsync 的那套编排——它不是"存一下、提交一下"这么糙,而是一个类 SAGA 的循环:

// UnitOfWork.CompleteAsync 的核心节奏
await SaveChangesAsync();                    // 1. 准备变更(还没提交)
// 2. 收集本地/分布式事件
while (LocalEvents.Count != 0 || DistributedEvents.Count != 0)
{
   
    await PublishLocalEventsAsync(...);      // 3. 发事件
    await PublishDistributedEventsAsync(...);//    事件处理器可能又产生新的变更
    await SaveChangesAsync();                //    于是再存一次,循环到没有新事件
}
await CommitTransactionsAsync();             // 4. 数据和事件都就绪,才真正提交
await OnCompletedAsync();                     // 5. 提交成功后再发"完成"事件
// 任何一步抛异常 → RollbackAllAsync() 全部回滚

配套还有一个 DataExecuting 的 AOP,SQL 执行前自动把 CreatedTime / ModifiedTime / TenantId / CreatedBy、雪花主键、甚至 TraceId 都填好。所以我的仓储层是干干净净的,没有一处手动赋值审计字段。

四、多租户是"三层隔离",不是随手加个 WHERE

租户这块我是当核心做的。框架支持库隔离 > 字段隔离 > 行隔离三种粒度:有独立连接就走库隔离,否则退回到用 TenantId 列做过滤。

字段/行隔离靠的是 SqlSugar 的全局 QueryFilter,一次配置、所有 SELECT/UPDATE/DELETE 透明生效:

// 软删除过滤
provider.QueryFilter.AddTableFilter<ISoftDelete>(e => !e.IsDeleted);

// 租户过滤:无租户上下文(运维) 不过滤;有上下文只看自己 + TenantId=0 的平台模板
provider.QueryFilter.AddTableFilter<IMultiTenantEntity>(
    e => currentTenant.Current == null
      || !currentTenant.Current.TenantId.HasValue
      || e.TenantId == currentTenant.Current.TenantId.Value
      || e.TenantId == 0);

TenantId = 0 我留给平台级模板数据(所有租户可读);运维场景可以主动清空租户上下文跨租户操作。另外 Update / Delete 之前会先按 Id 预读一次,读不到就直接拒——防的就是拿着别的租户的 Id 来改删数据这种越权。

五、一个 [Cacheable] 就是双层缓存 + 租户隔离 key

缓存走 .NET 内置的 HybridCache:L1 内存 + L2 Redis。方法打个特性就自动缓存,写操作用 [CacheEvict] 失效:

[Cacheable(Key = "config:{tenantId}", ExpireSeconds = 300)]
public Task<AppConfig> GetConfigAsync(long tenantId) {
    ... }

缓存 key 会被自动规范成 {TenantId}:{CacheName}:{UserKey},各租户的缓存空间天然隔离,不会串。L1 内存缓存我卡了个上限(最多 60s),避免节点内存被顶爆,L2 Redis 才按你设的过期时间来。

六、"优先 .NET 原生"不是口号:一个真·零依赖工具库

XiHan.Framework.Utils 我特意压成了零第三方依赖——.csproj 里一个 PackageReference 都没有,215 个源文件全部只用 System.*。加密用 System.Security.Cryptography,序列化用 System.Text.Json,网络用 System.Net

里面攒了不少我自己反复要用的东西,随手举几个真实存在的:

  • Base58 / Base62 / Base95 编码(Base58 用 BigInteger + ArrayPool,避 GC);
  • OtpHelper——TOTP/HOTP,兼容 Google Authenticator,2FA 直接能用;
  • AsyncReaderWriterLockRetryPolicy(指数退避 / 线性 / 固定间隔)、SortByDependencies 拓扑排序;
  • LunarCalendarHelper 农历/天干地支/节气(1900–2100)——本土化场景是真的会用到;
  • 还有个稍微皮一点的 ErrorObfuscation:检测到恶意访问时,给对方返回一段假的错误堆栈,能伪装成 Java/PHP/Python 等 8 种语言的报错,迷惑一下扫描器。

七、框架自带一个 Roslyn 分析器

XiHan.Framework.Analyzers 是个真的 Roslyn 分析器 + CodeFix,专门管文件头:没写规范文件头会报,还能一键修复,打包时塞进 analyzers/dotnet/cs。自己吃自己的狗粮,全仓库文件头才能保持一致。

还有这些(都是代码里跑着的)

篇幅有限,剩下的快速过一遍,都是代码里实打实在跑的:

  • 分布式 ID:雪花(位长可配、带时钟回拨自愈,不是回拨就抛异常了事)+ ULID + NanoID + SQID + 顺序 GUID,工厂预置了一堆并发档位;
  • 事件总线:本地 + 分布式,带 Outbox 可靠投递(事件先落库、后台服务批量拉取发送、按创建时间 FIFO),和 UoW 打通,业务和发事件保持原子;
  • 灰度发布:规则引擎 + 优先级 + 有效期窗口 + 可插拔匹配器(百分比 / 用户 / 租户 / IP / 请求头);
  • 入站限流 + 熔断:请求管道里有按 IP 的固定窗口限流,和一个三态熔断(滑动窗口统计 5xx,默认关,/health 豁免);
  • AI + MCP:这块最新也最有意思——你在应用里实现一个 IAiSkill,框架通过官方的 McpServerTool.Create 把它同时投影成两条通道:一条给 Microsoft.Extensions.AI 的对话函数调用,一条给 MCP 客户端(Claude Code / Cursor 之类)直接当工具用。基于 Semantic Kernel + Model Context Protocol;
  • Bot:邮件 / 短信 / Telegram / 钉钉 / 飞书 / 企业微信,拆成六个 Provider 子包,配置全部 store 化;
  • 升级引擎:分布式锁 + 主节点选举 + 维护模式 + 启动自检;
  • OpenAPI 安全:AccessKey/SecretKey + RSA / 国密 SM2 签名 + 内容加密 + IP 白名单;
  • 再加上统一响应封装、TraceId、请求上下文聚合、IP 地理定位(ip2region 离线库)、UA 解析、Redis 分布式锁/队列/延迟队列、gRPC、SignalR、Scalar + Swagger、国际化……

它也有这些"不完美"

挑几个我自己最清楚的短板说:

  • 主要是我一个人在维护,两年 1444 次提交,内核还在重构(仓库里那个 next/ 目录就是重构中的下一代内核),API 偶尔会有破坏性调整,升级前请留意 CHANGELOG;
  • 测试覆盖不均:Utils、Logging、Http、Web.Api 这些有测试,但加密、并发这些关键部分还欠账,上生产前自己再验一遍更稳;
  • 有些模块默认是内存实现:比如 Outbox、升级引擎的版本存储,默认走内存,适合开发和演示,生产得替换成数据库 / Redis 实现(接口都留好了);
  • Traffic 模块里那套通用的限流/熔断抽象目前还只是接口骨架——真正在跑的限流/熔断是在 Web 请求管道那一层,这两者别搞混;
  • AI 模块是 2026 年 7 月才加的,最新,也最"嫩",还在演进;
  • 文档滞后于代码:这点我认——新能力我常是先写进框架、文档隔一阵才补上,中间那段时间文档确实会落后。

技术栈 & 谁适合用

运行时:.NET 10 / C# latest(Roslyn 分析器包按规矩降到 netstandard2.0)
数据 & 缓存:SqlSugar(PostgreSQL / MySQL)/ HybridCache + Redis
基础设施:Serilog / Castle DynamicProxy(AOP) / Polly(HTTP 韧性) / Scriban(模板) / BouncyCastle(国密)
AI & 通信:Semantic Kernel + MCP / gRPC / SignalR
文档 & 测试:Scalar + Swashbuckle / xUnit v3

它可能适合你,如果你:

  • 想要一套能完全吃透、每行都看得懂的模块化底座,而不是一个黑盒;
  • 在学 .NET 后端进阶,想看模块系统、动态 API、DDD/CQRS、多租户、事件总线、Outbox 这些到底怎么落地;
  • 在做 B2B SaaS,需要租户隔离 + 工作单元 + 缓存 + 灰度这一整套。

跑起来就三步

dotnet add package XiHan.Framework.Core
dotnet add package XiHan.Framework.Web.Api
var builder = WebApplication.CreateBuilder(args);
await builder.AddApplicationAsync<MyAppModule>();   // 加载 + 拓扑排序所有模块

var app = builder.Build();
await app.InitializeApplicationAsync();             // 跑生命周期钩子
await app.RunAsync();

模块声明依赖、写应用服务、打 [DynamicApi],接口和文档就都有了。

写在最后

这套框架还会一直改下去,它不完美,但它是我自己每天在用的东西,不是写给人看的样板。如果它能让你少接一次中间件、少写一遍 ApiResponse,或者给你一点"自己造框架"的参考,我就很知足了。

想看某块的实现细节——比如动态 API 的 IL 发射、UoW 的事件+事务编排、多租户三层隔离怎么切——评论区告诉我,我可以单独拆一篇讲透。

目录
相关文章
|
1月前
|
人工智能 分布式计算 DataWorks
阿里云大数据 AI 产品月刊-2026年6月
阿里云大数据& AI 产品技术月刊【2026 年 6 月】,涵盖 6 月技术速递、产品和功能发布、市场和客户应用实践等内容,帮助您快速了解阿里云大数据& AI 方面最新动态。
|
1月前
|
JSON 缓存 自然语言处理
大模型长文本处理实践:基于分段提取与结果合并生成结构化摘要
本文提出一种高效、稳定的长文本处理方案:通过智能分段(支持句子级切分与上下文重叠)、并行结构化提取(JSON格式事实抽取)、多级去重(精确匹配+语义相似度)、冲突识别与溯源校验,最终生成可追溯、低成本、高可控的摘要。适用于录音转写、会议纪要等场景,显著提升准确性与可解释性。(
795 110
|
1月前
|
弹性计算 小程序 关系型数据库
一次真实录屏:我只说每月别超过 200 块,小程序后端就搭好了
iac-code 通过自然语言交互,自动规划、创建并管理符合预算的小程序后端云资源。本文结合真实录屏,展示它如何准备多套方案、给出架构与费用、在创建前等待确认,并在 RDS 规格下线后自动处理、继续部署,大幅降低阿里云的使用门槛。
一次真实录屏:我只说每月别超过 200 块,小程序后端就搭好了
|
1月前
|
人工智能 弹性计算 自然语言处理
阿里云产品与AI产品优惠信息参考:Token Plan、万小智AI建站、AI组合购等优惠信息汇总
阿里云最新云产品与AI产品优惠信息:覆盖基础云服务器、AI大模型、编程智能体、视频生成等全链路产品。核心优惠包括:百炼全模型通用Token Plan包季低至4.5折,覆盖150+款大模型;轻量应用服务器2核2G新人专享68元/年起,e实例99元/年续费同价;Qoder CN编程智能体59元/月起,秒悟Pro版限量9.9元/月,HappyHorse视频生成限时8折,万小智AI建站赠域名与灵感值。同时搭配最高100万元OPC创新助力Token补贴,全方位降低AI落地与上云综合成本。
|
1月前
|
SQL 人工智能 Serverless
从数据湖到多模态湖仓-基于阿里云 EMR Serverless StarRocks 与 DLF Paimon 构建AI时代的统一分析检索架构
阿里云 EMR Serverless StarRocks 在统一数据、一致语义和系统级优化之上,构建了面向 AI Data、AI Agent 和多模态应用的下一代湖仓架构。
从数据湖到多模态湖仓-基于阿里云 EMR Serverless StarRocks 与 DLF Paimon 构建AI时代的统一分析检索架构
|
1月前
|
缓存 UED 开发者
Codex109天重置23次,明天还要再送一次
Codex近109天完成23次额度重置,7月14日将迎来第24次。Tibo高频响应用户反馈:优化GPT-5.6高消耗问题、补发失效福利、调整重置时间——形成“反馈→回应→修复→补偿”正向闭环,彰显以用户为中心的产品哲学。(239字)
1646 12
|
1月前
|
机器学习/深度学习 运维 算法
路面缺陷检测数据集(9类YOLO已标注已划分)道路病害目标检测专用数据集分享
本数据集专为路面病害检测打造,含9类YOLO格式标注(含原生/修补裂缝、坑槽、网裂等),已划分训练/验证/测试集,适配YOLOv5-v10等主流模型。真实场景覆盖光照、雨湿、阴影等复杂条件,助力智能巡检、自动驾驶风险感知与道路养护数字化。
|
1月前
|
人工智能 弹性计算 运维
阿里云CLI询价——Agent 时代,让每一次"回车"都心里有数。
阿里云CLI上线“执行前询价”能力,支持`--estimate-cost`参数,精准预估每条命令费用。5步换IP场景实测,预估与实际账单分毫不差,助力AI Agent实现成本可控、财务可信的自动化运维。(239字)
201 4
|
18天前
|
人工智能 文字识别 监控
独家揭秘:TikTok直播测试团队如何用AI把跨国时区兼容性测试从3周压到4小时
TikTok直播团队用AI攻克跨国时区测试难题:构建三层自动化体系,将150国、24时区兼容性测试从3周压缩至4小时,线上时区事故下降82%,实现“一台设备模拟全球用户”。
|
1月前
|
存储 算法
Tushare接口文档:复权因子(adj_factor)
本文介绍的复权因子是“累计后复权因子”,存储的是每个交易日的快照。本文介绍了如何获取某个交易日全市场股票的复权因子、如何获取单只股票最近6000条复权因子、如何获取某段时间区间里的复权因子以及如何计算前复权和后复权价格。
326 8