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

发布于

zhaifanhua: 如果你写过几年 .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 、`HybridCache`、`System.Text.Json`、内置限流器),能不引第三方就不引;模块之间用 `[DependsOn]` 声明依赖、拓扑排序自动加载;应用服务打个特性就自动变 API 。

它也不是 demo —— 我那套多租户中后台 [XiHan.BasicApp](https://github.com/XiHanFun/XiHan.BasicApp) 就跑在它上面,是真刀真枪在用的。

## 几个我自己最满意的设计

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

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

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

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

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

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

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

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

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

```csharp
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/DELETE`,`Async` 后缀和动词前缀自动从路由里剥掉。这套映射表就写在 `DynamicApiConventionOptions` 里,你也能改。

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

```csharp
// 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 的循环:

```csharp
// 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 透明生效:

```csharp
// 软删除过滤
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]` 失效:

```csharp
[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`](http://system.net/)。

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

- `Base58 / Base62 / Base95` 编码( Base58 用 `BigInteger + ArrayPool`,避 GC );
- `OtpHelper`——TOTP/HOTP ,兼容 Google Authenticator ,2FA 直接能用;
- `AsyncReaderWriterLock`、`RetryPolicy`(指数退避 / 线性 / 固定间隔)、`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`](http://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 ,需要租户隔离 + 工作单元 + 缓存 + 灰度这一整套。

## 跑起来就三步

```bash
dotnet add package XiHan.Framework.Core
dotnet add package XiHan.Framework.Web.Api
```

```csharp
var builder = WebApplication.CreateBuilder(args);
await builder.AddApplicationAsync<MyAppModule>(); // 加载 + 拓扑排序所有模块

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

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

## 写在最后

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

- GitHub: [https://github.com/XiHanFun/XiHan.Framework](https://github.com/XiHanFun/XiHan.Framework)
- NuGet: [https://www.nuget.org/packages?q=XiHan.Framework](https://www.nuget.org/packages?q=XiHan.Framework)
- 源码导读: [https://deepwiki.com/XiHanFun/XiHan.Framework](https://deepwiki.com/XiHanFun/XiHan.Framework)
- 在线文档: [https://docs.xihanfun.com/](https://docs.xihanfun.com)
- 上层应用示例: [https://github.com/XiHanFun/XiHan.BasicApp](https://github.com/XiHanFun/XiHan.BasicApp)
- 交流 QQ 群:462371834
- Star 、Issue 、PR 、吐槽都欢迎。

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

---

原文链接:[点击查看](https://www.v2ex.com/t/1226998)

评论(1)

搞 .NET 的必须支持一下!顺便也分享个我之前弄的简单轮子:https://github.com/beginor/net-core-app,大家可以交流交流。

· 0 个赞