ASP.NET Core解析Protobuf格式数据
两个Nuget包
谷歌提供了
ASP.NET Core解析Protocol Buffers
ASP.NET Core默认只支持对application/json
的解析,要解析protobuf格式数据,需要引入nuget包:
此外,通过Grpc.Tools
生成的C#类型中,集合类型的属性是只读的,导致ASP.NET Core中默认的json formatter在进行模型绑定时,无法给集合类行属性赋值。AspCoreProtobufFormatters
包当前版本(1.0.0
版本)默认不支持application/json
格式,可以通过扩展来支持:
internal static class HttpContentType { public static class Application { public const string Json = "application/json"; } } /// <summary> /// 针对ContentType为<see cref="HttpContentType.Application.Json"/>类型数据的格式化器 /// </summary> internal class ProtobufApplicationJsonFormatter : ProtobufJsonFormatter { public ProtobufApplicationJsonFormatter() : base(HttpContentType.Application.Json) { } protected override (bool, byte[]) WriteBytes(IMessage message) => (true, Encoding.UTF8.GetBytes(ProtoModelTypeRegister.JsonFormatter.Format(message))); }
using Google.Protobuf; using Google.Protobuf.Reflection; namespace Models { public static class ProtoModelTypeRegister { public static readonly JsonFormatter JsonFormatter; static ProtoModelTypeRegister() { var messageTypes = typeof(ProtoModelTypeRegister).Assembly.GetTypes() .Where(t => t.IsAbstract == false) .Where(t => t.IsAssignableTo(typeof(IMessage))); var descriptorList = new List<MessageDescriptor>(); foreach (var msgType in messageTypes) { var descriptorProperty = msgType.GetProperty("Descriptor"); if (descriptorProperty == null) { continue; } if (descriptorProperty.GetValue(msgType) is MessageDescriptor messageDescriptor) { descriptorList.Add(messageDescriptor); } } var typeRegistry = TypeRegistry.FromMessages(descriptorList); JsonFormatter = new JsonFormatter(new JsonFormatter.Settings(true, typeRegistry)); } } }
在ASP.NET Core中添加引用:
builder.Services.AddControllers(opt => { opt.AddProtobufFormatters(new IContentReader[] { new ProtobufBinFormatter(), new ProtobufApplicationJsonFormatter() }, new IContentWriter[] { new ProtobufBinFormatter(), new ProtobufApplicationJsonFormatter() }); });
注意,这里添加formatter时,Protobuf formatter在前,json formatter在后,所以会优先选用protobuf formatter来格式化数据。如果想要返回json格式数据,可以根据内容协商机制在Accept头字段中指定application/json
。对于不支持内容协商的场景,可以通过自定义一个过滤器来实现:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] internal class EnableJsonResponseFilterAttribute : ResultFilterAttribute { public override void OnResultExecuting(ResultExecutingContext context) { if (context.HttpContext.Request.AcceptJson()) { // 执行response format之前将response content-type设为json格式 context.HttpContext.Response.ContentType = context.HttpContext.Request.ContentType!; } } } internal static class HttpExtensions { /// <summary> /// 判断当前HTTP请求的Content-Type中是否包含 <see cref="HttpContentType.Application.Json"/> /// </summary> public static bool ContentTypeIsJson(this HttpRequest request) { foreach (var contentType in request.Headers.ContentType) { if (contentType.Contains(HttpContentType.Application.Json, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } /// <summary> /// 判断当前HTTP请求是否接受Json格式的返回数据,目前只通过Content-Type来判断,忽略Accept /// </summary> public static bool AcceptJson(this HttpRequest request) { foreach (var accept in request.Headers.Accept) { if (accept.Contains(HttpContentType.Application.Json, StringComparison.OrdinalIgnoreCase)) { return true; } } return ContentTypeIsJson(request); } }
推荐阅读