在protobuf-net.Grpc.AspNetCore中使用接口作为[DataMember]的异常问题
问题场景
开发.NET项目时,用Protobuf和gRPC实现序列化与通信,类采用接口+[DataContract]特性配置,但调用接口时触发序列化错误。
相关代码
Config与Feature类
[DataContract] public partial class Config : IConfig { public Config() { } [DataMember(Order = 1)] public string Header { get; set; } = string.Empty; [DataMember(Order = 2)] public string Target { get; set; } = string.Empty; [DataMember(Order = 4)] public IEnumerable<IFeature> Features { get; set; } } [DataContract] public partial class Feature : IFeature { public Feature() { } public Feature(string name) { Name = name; } [DataMember(Order = 1)] public string Name { get; set; } [DataMember(Order = 2)] public bool IsEnabled { get; set; } public override string ToString() { return Name; } }
Program.cs代码
ProtoBuf.Meta.RuntimeTypeModel.Default.Add(typeof(IFeature), true).AddSubType(1, typeof(Feature)); // Generate Board Config Proto. GenerateBoardConfigProto(); var builder = WebApplication.CreateBuilder(args); builder.Services.AddCodeFirstGrpc(options => { options.EnableDetailedErrors = true; }); var app = builder.Build(); // Configure the HTTP request pipeline. app.MapGrpcService<BoardConfigurationService.Services.BoardConfigurationService>(); app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); app.Run();
报错信息
调用Postman时触发错误:
Exception was thrown by handler. ProtoException: No parameterless constructor found for TestAssets.Interface.Configuration.IFeature
MissingMethodException: Cannot dynamically create an instance of type 'TestAssets.Interface.Configuration.IFeature'. Reason: Cannot create an instance of an interface.
问题原因
Protobuf-net处理IEnumerable<IFeature>这类接口集合时,会尝试直接实例化接口IFeature,但接口本身无法被实例化——即便你注册了子类型映射,集合的序列化逻辑仍会优先尝试创建接口实例,导致报错。
解决步骤
1. 替换集合类型为具体实现
把Config类中的IEnumerable<IFeature>改成具体集合类型,比如List<Feature>:
[DataMember(Order = 4)] public List<Feature> Features { get; set; } = new List<Feature>();
这样Protobuf-net会直接处理具体类型,绕开实例化接口的逻辑。
2. 调整RuntimeTypeModel注册逻辑
确保在生成Proto或启动服务前完成类型映射注册,且注册顺序正确:
var typeModel = ProtoBuf.Meta.RuntimeTypeModel.Default; // 先注册接口,关闭自动添加成员(避免接口无成员导致的问题),再绑定子类型 typeModel.Add(typeof(IFeature), false) .AddSubType(1, typeof(Feature)); // 注册Config类型,自动识别[DataMember]标记的成员 typeModel.Add(typeof(Config), true);
3. 确保序列化类有无参构造函数
你的Feature类已经有公开无参构造函数,符合要求——所有参与序列化的类都必须保留无参构造函数(私有也可,但公开更稳妥)。
4. 配置gRPC Code-First与Protobuf-net集成
如果用gRPC Code-First,要确保Protobuf-net被设为默认序列化器,需先安装Grpc.AspNetCore.Server.CodeFirst和protobuf-net.Grpc NuGet包,再修改配置:
builder.Services.AddCodeFirstGrpc(options => { options.EnableDetailedErrors = true; // 启用Protobuf-net序列化 options.UseProtoBufNet(); // 可按需添加其他配置,比如消息大小限制 options.MaxReceiveMessageSize = 1024 * 1024 * 50; });
内容的提问来源于stack exchange,提问作者Justin CI

