Orleans 序列化配置实战:JSON 委托、类型名解析授权与存储序列化
后端微服务【免费下载链接】orleansCloud Native application framework for .NET项目地址https://gitcode.com/gh_mirrors/or/orleans点击查看免费下载序列化是 .NET Orleans 整体系统设计中至关重要的一环grain 调用、响应、请求上下文、持久化状态、提醒与流数据都要经过序列化在进程间传递。Orleans 虽然提供了开箱即用的默认序列化方案但在实际应用中你经常需要把特定类型委托给Newtonsoft.Json或System.Text.Json等外部序列化器并且必须理解注册序列化器与授权类型名解析是两件不同的事。本文以 serialization-configuration.md 为骨架结合本仓库源码完整讲解 JSON 序列化器配置、类型名解析安全边界与 grain 存储序列化的实操方案读完即可在 silo 与客户端两侧落地一套安全、可运行的序列化配置。序列化配置在 Orleans 中的定位Orleans 对序列化提供了合理的默认实现应用类型通过[GenerateSerializer]特性配合 Orleans 源代码生成器见 Orleans.CodeGenerator生成高性能的专用序列化器。但并非所有类型都适合由生成的序列化器处理典型场景包括多态契约中基类是抽象的具体运行时类型要到反序列化时才能确定类型结构复杂、频繁变动希望交给成熟的 JSON 库处理与既有系统对接必须使用某种既定序列化格式。针对这些场景Orleans 在消息传输路径上支持将序列化工作委托给其他序列化器例如Newtonsoft.Json与System.Text.Json。Orleans.Serialization的源码Orleans.Serialization.NewtonsoftJson/SerializationHostingExtensions.cs、Orleans.Serialization.SystemTextJson/SerializationHostingExtensions.cs展示了这两套官方实现的完整模式你可以照此模式为其他序列化库编写适配器。需要注意两条路径的区分主机间数据传输grain 调用参数、返回值、流数据等通过ISerializerBuilder注册 codec 委托给外部序列化器grain 持久化存储最佳实践是使用IGrainStorageSerializer配置自定义存储序列化器而不是依赖消息传输层的 codec 选择逻辑。配置 Orleans 使用Newtonsoft.Json安装与基本配置首先在目标项目中引用Microsoft.Orleans.Serialization.NewtonsoftJsonNuGet 包对应本仓库的 Orleans.Serialization.NewtonsoftJson.csproj。然后配置序列化器并明确指定它负责哪些类型。下面这个来自文档片段 TypeNameResolutionExamples.cs 的示例让Newtonsoft.Json负责Example.Namespace命名空间下的所有类型siloBuilder.Services.AddSerializer(serializerBuilder { serializerBuilder.AddNewtonsoftJsonSerializer( isSupported: type type.Namespace?.StartsWith(Example.Namespace, StringComparison.Ordinal) is true); });AddNewtonsoftJsonSerializer调用Orleans.Serialization.SerializationHostingExtensions为使用Newtonsoft.Json.JsonSerializer的序列化与反序列化添加支持。所有需要处理这些类型的客户端必须做相同的配置——客户端通过IClientBuilder即UseOrleansClient回调中的 builder执行同样的AddSerializer调用否则客户端与 silo 之间的 codec 选择不一致会导致消息无法解析。谓词与选项的完整形态从 SerializationHostingExtensions.cs 的实现可以看到AddNewtonsoftJsonSerializer提供了三个重载核心参数语义如下isSupported即isSerializableFuncType, bool委托决定哪些类型由该 codec 负责序列化/反序列化。注册时被包装为DelegateCodecSelector并注册为ICodecSelector单例isCopyable可选的第二个谓词决定哪些类型由该 codec 负责深拷贝。注册时被包装为DelegateCopierSelector并注册为ICopierSelector单例。若不单独指定默认与序列化谓词一致configureOptions/jsonSerializerSettings配置NewtonsoftJsonCodecOptions。NewtonsoftJsonCodecOptions.SerializerSettings见 NewtonsoftJsonCodecOptions.cs允许你传入完整的JsonSerializerSettings同时选项类还提供IsSerializableType与IsCopyableType两个可选的FuncType, bool?属性供 codec 内部二次判定。因此更完整的配置形态是分别控制序列化与拷贝范围并自定义JsonSerializerSettingssiloBuilder.Services.AddSerializer(serializerBuilder { serializerBuilder.AddNewtonsoftJsonSerializer( isSerializable: type type.Namespace?.StartsWith(Example.Namespace, StringComparison.Ordinal) is true, isCopyable: type type.Namespace?.StartsWith(Example.Namespace, StringComparison.Ordinal) is true, options options.Configure(o { o.SerializerSettings new JsonSerializerSettings { TypeNameHandling TypeNameHandling.None, NullValueHandling NullValueHandling.Ignore, }; })); });底层注册机制在AddNewtonsoftJsonSerializer内部首次调用时会一次性完成以下注册幂等通过ServiceDescriptor判重注册NewtonsoftJsonCodec单例通过AddFromExisting将其同时暴露为IGeneralizedCodec通用 codec、IGeneralizedCopier通用 copier与ITypeFilter类型过滤器三个服务在TypeManifestOptions.WellKnownTypeAliases中登记NewtonsoftJsonCodec.WellKnownAlias。这意味着 Newtonsoft 序列化器不仅负责读写字段还参与值的深拷贝并在类型解析阶段作为ITypeFilter提供意见——这一点在理解下文类型名解析授权时非常关键。与生成序列化器的优先级对于标记了[GenerateSerializer]的类型Orleans优先使用生成的序列化器而不是Newtonsoft.Json序列化器。换句话说isSupported谓词只会接管那些没有专用生成序列化器的类型。这是设计上的刻意行为生成序列化器更高效、更安全外部 JSON 序列化器只作为补充手段。配置 Orleans 使用System.Text.Json安装与基本配置作为替代方案可以引用Microsoft.Orleans.Serialization.SystemTextJsonNuGet 包对应 Orleans.Serialization.SystemTextJson.csproj然后调用AddJsonSerializer方法配置序列化器。下面的示例来自 TypeNameResolutionExamples.cs让System.Text.Json负责Example.Namespace命名空间下的所有类型siloBuilder.Services.AddSerializer(serializerBuilder { serializerBuilder.AddJsonSerializer( isSupported: type type.Namespace?.StartsWith(Example.Namespace, StringComparison.Ordinal) is true); });在 silo 一侧通过ISiloBuilderUseOrleans回调完成上述配置在客户端一侧则在IClientBuilderUseOrleansClient回调中做同样配置。选项定制SerializerOptions / ReaderOptions / WriterOptions与 Newtonsoft 版本对称AddJsonSerializer也提供isSupported、isSerializable/isCopyable、configureOptions等多个重载见 Orleans.Serialization.SystemTextJson/SerializationHostingExtensions.cs。其底层同样将JsonCodec注册为IGeneralizedCodec、IGeneralizedCopier与ITypeFilter。JsonCodecOptions.cs 提供了三个可配置的选项对象SerializerOptionsJsonSerializerOptions默认new()控制属性命名策略、忽略规则、转换器集合等ReaderOptionsJsonReaderOptions控制读取时的注释/尾随逗号处理等WriterOptionsJsonWriterOptions控制输出缩进、编码等。例如开启 camelCase 属性命名并忽略 null 值siloBuilder.Services.AddSerializer(serializerBuilder { serializerBuilder.AddJsonSerializer( isSupported: type type.Namespace?.StartsWith(Example.Namespace, StringComparison.Ordinal) is true, options options.Configure(o { o.SerializerOptions new JsonSerializerOptions { PropertyNamingPolicy JsonNamingPolicy.CamelCase, DefaultIgnoreCondition JsonIgnoreCondition.WhenWritingNull, }; })); });授权类型名解析注册序列化器不等于信任类型这是本主题中最容易踩坑、也最需要重视的安全边界。注册一个外部序列化器只是选择了由哪个 codec 来处理值它并不会自动授权 Orleans 去解析该序列化器所能接受的每一个 CLR 类型名。类型名解析是独立的安全边界且TypeManifestOptions.AllowAllTypes的默认值是false见 TypeManifestOptions.cs。这一点在多态签名下尤其明显。考虑IReadOnlyListTriggerRule这样的契约其中TriggerRule是抽象类具体值由System.Text.Json处理文档示例见 HostSnippets.cssiloBuilder.Services.AddSerializer(serializerBuilder { serializerBuilder.AddJsonSerializer( isSupported: type type.Namespace?.StartsWith(MyApp, StringComparison.Ordinal) true); serializerBuilder.Configure(options options.AddAllowedType(typeof(TriggerRule))); });这段代码做了两件事注册 JSON 序列化器处理MyApp命名空间的类型同时显式信任应用类型TriggerRule允许 Orleans 在收到其运行时类型名时解析该类型。AddAllowedType 与 AllowedTypesTypeManifestOptions.AddAllowedType(Type)是添加单个类型信任的首选方式。从其实现TypeManifestOptions.cs可以看到public void AddAllowedType(Type type) { if (type is null) throw new ArgumentNullException(nameof(type)); AllowedTypes.Add(RuntimeTypeNameFormatter.FormatInternalNoCache(type, allowAliases: false)); }它使用 Orleans 的运行时类型名格式化器RuntimeTypeNameFormatter生成条目覆盖构造泛型与嵌套泛型类型的完整形态且allowAliases: false意味着不使用复合别名避免后续别名配置影响已登记的条目。AllowedTypes字符串集合HashSetstringStringComparer.Ordinal大小写敏感比较见 TypeManifestOptions.cs为兼容性而保留其中存放的是 Orleans 格式的运行时类型名CLR 类型名语法含命名空间、嵌套、泛型参数、数组与可选的程序集限定名。优先使用AddAllowedType而不是手工拼接这些名称——手工拼接极易因格式细节出错且会在别名配置变化时产生不一致。若确实需要字符串形式应使用RuntimeTypeNameFormatter.Format(type)生成后再加入集合。程序集信任AddAllowedAssembly如果应用程序集中的所有类型都可信任可以直接信任整个程序集示例见 HostSnippets.cssiloBuilder.Services.AddSerializer(serializerBuilder { serializerBuilder.Configure(options options.AddAllowedAssembly(typeof(TriggerRule).Assembly)); });AddAllowedAssembly通过CachedTypeResolver.GetName(assembly)将程序集名加入AllowedAssemblies集合。程序集信任是逐组件生效的允许一个泛型类型定义的所在程序集并不会隐式信任来自其他程序集的泛型实参。也就是说对于ListFoo即使ListT定义在已信任的程序集中Foo所在的程序集也必须单独受信任数组元素类型同理必须逐个组件检查并受信任。基于策略的信任ITypeNameFilter当信任策略复杂、无法用允许某个类型/程序集表达时可以实现ITypeNameFilter接口在 Orleans 加载对应类型之前对类型名进行评估。文档示例 TypeNameResolutionExamples.cs 中的过滤器如下public sealed class ApplicationTypeNameFilter : ITypeNameFilter { public bool? IsTypeNameAllowed(string typeName, string assemblyName) { if (assemblyName MyApp.Contracts || assemblyName.StartsWith(MyApp.Contracts,, StringComparison.Ordinal)) { return true; } return null; } }通过依赖注入注册过滤器TypeNameResolutionExamples.cssiloBuilder.Services.AddSingletonITypeNameFilter, ApplicationTypeNameFilter();IsTypeNameAllowed的返回值语义是true允许false拒绝null该过滤器对此名称没有意见。信任裁决的优先级规则需要准确掌握这也是 serialization-configuration.md 的核心要点显式加入AllowedTypes的类型是权威的直接放行对于其他名称任何一个ITypeNameFilter的拒绝都优先于其他类型名过滤器与程序集信任denial wins——也就是说只要有一个过滤器说拒绝即使另一个过滤器同意、或者该程序集已被信任也会被拒绝当基于名称的检查没有肯定性结果时ITypeFilter提供基于已解析Type的回退判定且在该回退路径中拒绝依然优先这些检查在格式化写入与解析读取两个方向都会执行并且覆盖构造泛型的各个组成部分与数组元素类型。这种拒绝优先、白名单权威的设计保证安全策略不会被多个过滤器的分歧所削弱。兼容性逃生舱AllowAllTypes作为兼容性逃生舱可以显式关闭该边界TypeNameResolutionExamples.cssiloBuilder.Services.AddSerializer(serializerBuilder { serializerBuilder.Configure((TypeManifestOptions options) options.AllowAllTypes true); });警告AllowAllTypes会绕过类型名验证包括自定义过滤器允许解析任何可解析的类型。仅在序列化输入完全可信时才可使用。更安全的做法始终是优先允许单个类型AddAllowedType或受信任的程序集AddAllowedAssembly。从安全角度看完整指导见 serialization security开放的类型解析面会让输入选择到应用声明消息契约之外的类型上的代码路径。在配置了外部序列化器且输入可被低信任方影响客户端、流、队列、存储系统时务必保持AllowAllTypes false并用上述机制收敛类型面。存储侧的序列化IGrainStorageSerializer上述 JSON 委托机制解决的是主机间消息传输的序列化。对于grain 持久化存储官方建议的最佳实践是使用IGrainStorageSerializer来配置自定义序列化器。这与消息序列化是两条独立的配置路径。以 Azure Blob 存储为例AzureBlobStorageGrainStorageProviderBuilder.cs 中的实现展示了存储序列化器的接入方式options.GrainStorageSerializer services.GetRequiredKeyedServiceIGrainStorageSerializer(serializerKey);即存储提供者通过键控服务keyed service解析IGrainStorageSerializer将其注入存储选项。这意味着你可以为不同存储、不同数据分区注册不同的键控序列化器实现按需选择格式例如 JSON、二进制等。Azure Table 存储提供者AzureTableStorageGrainStorageProviderBuilder.cs采用同样的模式DynamoDB、AdoNet 等提供者也一致地支持该接口参见 DynamoDBStorageOptions.cs、AdoNetGrainStorageOptions.cs 等。需要再次强调存储与消息传输的序列化器配置必须一致地部署在 silo 与客户端两侧。安全文档 serialization.md 明确建议将IsSupportedType谓词限制在预期的契约范围内把命名空间前缀选择与显式类型策略配对使用并在所有处理该契约的 silo 与客户端上配置相同的兼容序列化策略。最佳实践小结综合文档与源码落地一套安全、可维护的序列化配置时请遵循以下原则优先使用生成序列化器应用自有的 grain 契约类型用[GenerateSerializer] 源代码生成器外部 JSON 序列化器只负责补充场景委托范围收窄isSupported/isSerializable谓词尽量用精确的命名空间前缀或类型条件限定不要无条件委托类型解析边界保持关闭维持AllowAllTypes false对多态类型用AddAllowedType对整个可信程序集用AddAllowedAssembly复杂策略用过滤器实现ITypeNameFilter名称级并按需组合ITypeFilter类型级回退牢记拒绝优先于同意与程序集信任客户端与 silo 配置对称所有处理同一契约的 silo 和客户端必须配置相同的序列化器与类型策略存储序列化走专用通道grain 持久化使用IGrainStorageSerializer按存储提供者配置而不是依赖消息 codec反序列化后仍要校验数据成功的反序列化只证明字节符合格式与类型策略标识符、长度、范围、状态迁移等业务不变量必须在修改 grain 状态或调用依赖之前验证详见 serialization security。参考资源本文依据的主文档serialization-configuration.md完整可编译示例TypeNameResolutionExamples.cs 与 HostSnippets.csNewtonsoft.Json 适配实现Orleans.Serialization.NewtonsoftJsonSystem.Text.Json 适配实现Orleans.Serialization.SystemTextJson类型清单与信任选项TypeManifestOptions.cs序列化安全完整指南serialization security赞分享后端微服务【免费下载链接】orleansCloud Native application framework for .NET项目地址https://gitcode.com/gh_mirrors/or/orleans点击查看免费下载相关推荐Fabric.js序列化与反序列化JSON数据的存储与恢复Fabric.js序列化与反序列化JSON数据的存储与恢复 Fabric.js是一个强大的HTML5 Canvas库它提供了完整的对象序列化和反序列化功能前端图形学AutoCLI与autocli.ai云平台如何发现和共享社区适配器资源AutoCLI与autocli.ai云平台如何发现和共享社区适配器资源 AutoCLI是一款功能强大的命令行工具能够从超过60个网站和平台快速获取信息同时网页爬虫CLIRetrofit Gson Converter 完全指南JSON 序列化与反序列化的配置、原理与实战Retrofit Gson Converter 完全指南JSON 序列化与反序列化的配置、原理与实战 导读 retrofit converters/gson网络API设计上一篇NCM 转 MP3 最简单免费的方法免安装 exe 拖一下就转好下一篇猫抓cat-catch资源嗅探浏览器扩展快速上手教程3步搞定网页视频批量下载创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考