ASP.NET Core通过IInputFormatter来解析输入的数据,并进行模型绑定(Model Binding);通过IOutputFormatter来解析输出的数据,来格式化响应(format response)。
谷歌提供了Google.Protobuf包用于解析Protocol Buffers数据,包括和json格式互转;Grpc.Tools包可根据proto文件在编译时生成对应的c#/c++文件。
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);
}
}