ASP.NET Core解析Protobuf格式数据

ASP.NET Core通过IInputFormatter来解析输入的数据,并进行模型绑定(Model Binding);通过IOutputFormatter来解析输出的数据,来格式化响应(format response)。

 

两个Nuget包

谷歌提供了Google.Protobuf包用于解析Protocol Buffers数据,包括和json格式互转;Grpc.Tools包可根据proto文件在编译时生成对应的c#/c++文件。

 

ASP.NET Core解析Protocol Buffers

ASP.NET Core默认只支持对application/json的解析,要解析protobuf格式数据,需要引入nuget包:AspCoreProtobufFormatters,该包依赖Google.Protobuf包解析protobuf格式数据。

此外,通过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)));
}

 

将protobuf格式(IMessage类型)数据序列化为json格式时,需要将先注册相应的类型:

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);
    }
}
 

 

推荐阅读

Protocol Buffers

Custom formatters in ASP.NET Core Web API

Format response data in ASP.NET Core Web API

posted @ 2022-08-08 10:40  雪飞鸿  阅读(390)  评论(0编辑  收藏  举报