Vane.Dispatch 1.0.0 发布:一个与容器、传输层零耦合的 .NET 服务分发引擎

Vane.Dispatch 1.0.0 发布:一个与容器、传输层零耦合的 .NET 服务分发引擎

它的前身,可以追溯到一个叫 Ndf 的项目。从那时算起,已经过去了十多年。今天,它以 Vane.Dispatch 1.0.0 的名字正式发布。

序:十年磨一剑

做后端的人大抵都绕不开同一个问题:如何把"一个调用请求"干净地路由到"一段业务代码",并且把参数绑定、校验、过滤、异常这些横切关注点从业务里剥离出来。

十多年前,这个项目最早以 Ndf 的雏形出现,承载着同样的想法。中间几经搁置、重写,直到 .NET 跨入 net8.0 / net10.0 时代,AOT、裁剪、源生成成为 mainstream 诉求,那个老想法才终于找到了最贴合的形态。2026-09-06,Vane.Dispatch 1.0.0 作为首个正式开源版本发布(此前 Ndf 已在公司内部多个项目中长期生产使用)。

本文基于仓库中的全部源码与文档(READMEdeveloper-guide.md、三个示例、33 个测试文件)写成,力求每一个结论都能在代码里落地。

它是什么

Vane.Dispatch 是一个微内核 :按特性([Route])把调用路由到控制器操作,做模型绑定,跑过滤器管线,并基于接口生成代理。它只依赖一个最小的 ServiceResolver 委托,与任何 DI 容器、任何通信层都零耦合

你可以自带解析器(或任意 IServiceProvider)、自带控制器、自带线格式。库本体仅依赖 net8.0 BCL,双目标 net8.0;net10.0,所有公开类型统一归属 Vane.Dispatch 命名空间。

它的定位不是"又一个 Web 框架",而是对标 ASP.NET Core MVC 的语义,但不绑定 HTTP 的契约分发引擎。这句话是理解整个库设计取舍的钥匙。

核心设计原则

原则 含义
容器无关 唯一耦合点是 ServiceResolver 委托;用任意 IServiceProvider 包一层即可,无需引入额外容器
传输层无关 路由/绑定/过滤器/格式化全部在内存里完成;HTTP、gRPC、二进制协议都只是"把字节适配成 DispatchRequest"的宿主
AOT 友好 IsAotCompatible / IsTrimmable 已开启;反射入口均有 [RequiresUnreferencedCode] / [RequiresDynamicCode] / [DynamicallyAccessedMembers] 标注
同步快路径 + 按需异步 默认全同步、Task 无关的零分配快路径;仅在处理器真正做 I/O 时切到 DispatchAsync

五分钟跑通

csharp 复制代码
using Vane.Dispatch;

[Route("math")]
public class MathController
{
    public int Add(int a, int b) => a + b;
    public int Square(int x) => x * x;
}

// 极简解析器:仅作演示,不引入任何 DI 容器
ServiceResolver resolver = type =>
    type.GetConstructor(Type.EmptyTypes)?.Invoke(null)
    ?? throw new MissingMethodException(type.FullName);

var engine = resolver
    .BuildDispatchEngine()
    .MapDispatchable<MathController>()
    .Build();

int sum = engine.Dispatch<int>("math", "Add", new { a = 2, b = 3 });   // 5
int sq  = engine.Send<MathController, int>("Square", 6);              // 36

public interface IMath { int Square(int x); }
var proxy = engine.CreateProxy<IMath>();
int px = proxy.Square(9);                                             // 81

如果你已经有 IServiceProvider,直接 services.BuildDispatchEngine() ------ BCL 适配器会把你的 provider 包成解析委托,无需引入 Vane.Container

特性详解

1. 特性路由(Attribute Routing)

类级 [Route] 同时承担三件事:可分发声明 + 路由组名 + 默认自动绑定推断 (对标 MVC 的 Controller + [Route] + [ApiController])。方法级 [Route] 是操作契约名 / 别名。查表大小写不敏感,构建期做重复操作检测(fail-fast)。[NoAutoBind] 可在类级关闭自动绑定。

2. 模型绑定:六种来源一次看懂

[FromBody] / [FromRoute] / [FromHeader] / [FromQuery] / [FromServices],外加具名参数与复杂对象递归绑定。[FromRoute]/[FromHeader]/[FromQuery] 严格来源隔离 ------[FromHeader] 即使路由值里存在同名键也不会命中。

csharp 复制代码
[Route("binder")][NoAutoBind]
public sealed class BinderController
{
    public byte[] EchoBody([FromBody] byte[] data) => data;     // BodyModelBinder
    public string Service([FromServices] IGreeter dep) => dep.Greet(); // ServicesModelBinder
    public int QueryVal([FromQuery] int q) => q;                // ValueProviderModelBinder
    public int RouteVal([FromRoute] int id) => id;
}

默认 [Route] 还按类型自动推断:复杂 DTO → Body、接口 → Services、简单类型 → Payload,无需逐个写 [From*]

3. 过滤器管线(对标 MVC 三态 OnExecuted

全局与操作级过滤器。OnExecuted成功 / 异常 / 取消三种状态下恒运行 ,上下文携带 Exception / Canceled / ExceptionHandled,与 MVC 的 OnActionExecuted 语义一致。IExceptionFilter 可置 ExceptionHandled 免除失败判定。

内置 UseValidation() 一键启用 DataAnnotations 校验(含 IValidatableObject),错误聚合到统一 ModelState → 400。默认零校验开销:不装配就不介入。

4. 格式化器与接口代理

JSON、原始字节、扁平二进制三种内置输入/输出格式,可插入 MessagePack 等自定义二进制协议。

CreateProxy<T>() 把任意接口变成进程内强类型客户端,走同一套分发内核(路由/绑定/过滤器),热路径零反射。

5. 自由端点(Minimal API 风格)

Map / MapGet / MapPost / Handle 注册委托端点,支持 {name} 路径段捕获:

csharp 复制代码
var engine = resolver.BuildDispatchEngine()
    .MapGet("/ping", () => "pong")
    .MapPost("/echo", (string msg) => msg)
    .Build();

6. 异步与取消

DispatchAsync 复用同一同步内核(路由/绑定/同步过滤器原样同步),仅对 Task / ValueTask 处理器 await,并注入 CancellationToken。令牌取消统一映射为 499RequestCanceled)。

7. 错误模型

DispatchResponse 是唯一响应载体,内置 Ok(200) BadRequest(400) NotFound(404) RequestCanceled(499) InternalServerError(500)ProblemDetails(RFC 7807)在成功时为 null(零分配),失败时惰性生成供跨进程/HTTP 宿主序列化。

性能与 AOT

  • 路由表预构建 ,热路径分发用表达式编译调用器而非反射;同步路径目标零分配确定性延迟。
  • 值提供器 / 绑定 / 路由是纯 CPU + 内存操作,刻意保持同步------没有可重叠的阻塞点,套 async 只增加状态机成本。
  • 异步 invoker 惰性编译,纯同步应用不产生常驻异步委托成本。
  • 库开启 XML 文档注释且未抑制 CS1591,构建保持 0 警告TreatWarningsAsErrors 已开)。

对标 MVC,而不是复刻 HTTP

这是库里最值得说清的一点。作者没有把 MVC 搬过来,而是做了一份克制的差异清单docs/MVC-Dispatch-Gap-Plan.md):

  • 不做[HttpGet]/[HttpPost] 动词约束、内容协商、IActionResult 体系、鉴权/资源/结果三类过滤器、IFormFile 表单绑定、{id:int} 路由类型约束。
  • 原因:Vane 没有 HTTP,这些语义本就不存在;直接返回 POCO 反而是定位优势。
  • 已等价具备[ApiController] 自动 400、UseValidation() 批量校验、IExceptionFilter、结构化 ProblemDetails

"对标是为了认知零障碍迁移,不是复刻 HTTP"------这条原则贯穿了整个 API 设计。

零传输耦合的真实证据

examples/Vane.Dispatch.HttpExampleSystem.Net.HttpListener(仅 BCL)把 HTTP 请求适配成 DispatchRequest,再经同一引擎完成绑定 + 过滤 + 表达式编译调用,响应经 IOutputFormatter 序列化。整个宿主只有约 150 行,没有引用任何第三方 HTTP 框架------这就是"传输层无关"不是口号的证明。

起步与获取

bash 复制代码
dotnet add package Vane.Dispatch
bash 复制代码
dotnet build Vane.Dispatch.slnx -c Release
dotnet test  tests/Vane.Dispatch.Tests -c Release
  • 源码与文档:https://gitee.com/netcasewqs/vane-dispatch
  • 许可:MIT
  • 作者:wangqingsong

结语

从 Ndf 到 Vane.Dispatch,变的是运行时的形态,不变的是"把分发逻辑做薄、做纯、做快"的执念。1.0.0 只是起点------它现在对标 MVC 语义、拥抱 AOT,下一步是把这条微内核路线在更多传输层上跑起来。

如果你也在做服务分发、RPC 网关、或想给一套老系统换一个轻量内核,欢迎试一试,也欢迎提 Issue。

相关推荐
沐沐师3 小时前
Nest.js 微服务入门教程
微服务·nestjs
万象观察录9 小时前
微服务架构下的API全生命周期安全防护办法
安全·微服务·架构
张人玉11 小时前
基于 C# WinForms + .NET 8 + SQLite + TCP Socket 的多连接通讯调试工具——TCP通讯助手(TcpAssistant)
tcp/ip·sqlite·c#·.net
mldong19 小时前
C# 开发者也有自己的轻量工作流引擎了:NuGet 装包,5 分钟跑通一条审批流
后端·c#·.net
风云1 天前
MiniLog 1.0 GA 发布:零分配、零依赖的 .NET 高性能日志框架
开源·c#·.net·日志·高性能·minilog
慧都小妮子1 天前
C# 工程图纸数字化入库实战:Aspose.Imaging 自动抠图、多页 TIFF 合卷与坏页容错
图像处理·c#·.net·aspose·tiff·归档·文档管理
夏天拐跑了西瓜1 天前
Spring Cloud 微服务实战(八):Hystrix熔断降级——从服务雪崩到断路器模式
spring cloud·hystrix·微服务
vHelios1 天前
【电商项目】短信测试遇到的问题与解决方案:InaccessibleObjectException报错、单元测试通过但未接收到验证码
java·微服务
事圆则缓1 天前
MVC、MVP、MVVM、MVI 的区别与实现原理
android·kotlin·mvc