XiHan Framework 处于预览阶段

查看获取方式
跳转到内容

序列化与 Native AOT ​

本章说明框架为什么要求源生成的序列化、哪些地方需要登记 JsonSerializerContext、哪些包兼容 Native AOT,以及发布 AOT 应用的步骤。适用于所有写操作、领域事件与缓存的开发者,以及要以 Native AOT 发布的团队。

框架不提供对象映射与序列化封装,序列化直接使用 System.Text.Json。

零反射的约束 ​

原语层、内核与全部抽象包不使用运行时反射:服务显式注册,操作与仓储的登记代码由源生成器产出,配置按键显式读取而不用反射绑定,JSON 编解码只经源生成的类型元数据(JsonTypeInfo)。这些包的项目都设置了 IsAotCompatible,裁剪与 AOT 分析器的警告在构建时视为错误。

因此应用与框架交换的类型要在编译期登记到 JsonSerializerContext:

用途登记方式
操作的请求与响应项目里任一 JsonSerializerContext 上的 [JsonSerializable];Kernel.Generators 找不到时报 XHG002
领域事件映射成消息AddDomainEvent<T>(topic, type, jsonTypeInfo, subject) 的 JsonTypeInfo<T> 参数
共享缓存的值容器里注册带 TypeInfoResolver 的 JsonSerializerOptions,或直接注册 ICacheSerializer
最小 API 端点的返回值ConfigureHttpJsonOptions 把上下文插到解析链最前面
csharp
using System.Text.Json.Serialization;

namespace Shop.Orders;

public sealed record PlaceOrderRequest(string Name, int Amount);

public sealed record OrderResponse(Guid Id, string Name, int Amount);

public sealed record OrderPlaced(Guid OrderId, DateTimeOffset At);

[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, PropertyNameCaseInsensitive = true, NumberHandling = JsonNumberHandling.AllowReadingFromString)]
[JsonSerializable(typeof(PlaceOrderRequest))]
[JsonSerializable(typeof(OrderResponse))]
[JsonSerializable(typeof(OrderPlaced))]
internal sealed partial class ShopJsonContext : JsonSerializerContext;

示例选项的含义:属性名用 camelCase;读取不区分大小写;允许从字符串读数字,因为 HTTP 适配器把路由值与查询串(都是字符串)合并进请求对象。

共享缓存的序列化器按以下优先级确定:容器里已有 ICacheSerializer 时用它;否则已注册 JsonSerializerOptions 时用它创建 JsonCacheSerializer;两者都没有时,非 AOT 的 Provider(如 Caching.Redis)退回反射版序列化器。AOT 应用必须走前两种方式,未登记的类型会抛出 NotSupportedException,不会静默降级。

包的 AOT 兼容性 ​

兼容 Native AOT不兼容 Native AOT
Primitives、Kernel、Domain 与全部 .AbstractionsPersistence.EntityFrameworkCore、Persistence.SqlSugar
全部 InMemory 默认实现、ObjectStorage.FileSystemCaching.Redis、Caching.Remote
MultiTenancy.Database、Security.Database、Localization.Database 等 Database 包Security.Jwt、Security.OpenIdConnect
Hosting.AspNetCore、Hosting.CommandLineMessaging.RabbitMQ、Messaging.Kafka、ObjectStorage.S3
Protocols.Http、Protocols.Grpc、Protocols.Mcp、Protocols.CloudEventsAI.OpenAI、AI.VectorData
Profiles.Minimal、Profiles.Standard、Profiles.FullPlugins.Protocol、Plugins.InProcess、Plugins.OutOfProcess、Plugins.Wasm

表中「兼容」以各项目的 IsAotCompatible 设置为准,各包页的信息表也标出了这一项。Database 包本身兼容,但它们经仓储访问数据库,实际能否 AOT 取决于应用选中的持久化 Provider:当前的 EF Core 与 SqlSugar Provider 都不兼容,Persistence.InMemory 兼容。

Plugins.InProcess 是框架里唯一允许反射的地方(加载插件程序集),相关 API 标注了 RequiresDynamicCode。

发布 AOT 应用 ​

仓库的 framework/samples/SimpleApp 是 AOT 冒烟项目,项目文件里声明了 PublishAot,CI 每次都以 Native AOT 发布它。按它的写法:

  1. 只引用兼容 AOT 的包,持久化用 Persistence.InMemory 或自己实现兼容 AOT 的 Provider。

  2. 项目文件声明 PublishAot,并打开最小 API 的请求委托生成器,去掉端点参数绑定里的反射:

    xml
    <PropertyGroup>
      <PublishAot>true</PublishAot>
      <EnableRequestDelegateGenerator>true</EnableRequestDelegateGenerator>
    </PropertyGroup>

    PublishAot 要写在项目文件里,不要在命令行传 -p:PublishAot=true:命令行属性是全局属性,会一路传给引用的项目。引用了 netstandard2.0 的分析器或生成器项目(如 Kernel.Generators)时,这些项目会报 NETSDK1207。

  3. 把源生成的上下文放到 HTTP JSON 解析链最前面,端点返回的类型不走反射序列化:

    csharp
    builder.Services.ConfigureHttpJsonOptions(options =>
        options.SerializerOptions.TypeInfoResolverChain.Insert(0, ShopJsonContext.Default));
  4. 应用自己的配置类需要绑定时,打开配置绑定源生成器(<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>),或像框架一样按键读取。

  5. 发布:

    bash
    dotnet publish -c Release -o ./publish

发布时出现的裁剪或 AOT 警告都要处理,不要用 NoWarn 压掉;它们通常指向某个未登记的序列化类型或引用了不兼容 AOT 的包。

对象映射 ​

框架不提供对象映射组件。实体与响应之间的转换写成显式的构造或静态方法,例如 new OrderResponse(order.Id, order.Name, order.Amount);需要大量映射时选用编译期生成代码的映射库,保持零反射。

相关 ​

Released under The MIT License