ASP.NET Core 使用protobuf在一些性能要求很高的应用中,使用protocol buffer序列化,优于Json。而且protocol buffer向后兼容的能力比较好。
由于Asp.net core 采用了全新的MiddleWare方式,因此使用protobuf序列化,只需要使用Protobuf-net修饰需要序列化的对象,并在MVC初始化的时候增加相应的Formatter就可以了。
MVC Controller 的Action返回对象时,MVC回根据用户的Request Header里面的MIME选择对应的Formater来序列化返回对象( Serialize returned object)。
MVC具有默认的Json Formater,这个可以不用管。
这里有一个直接可以运行的例子,具有Server和Client代码
https://github.com/damienbod/AspNetMvc6ProtobufFormatters
但是,这里面有一个很严重的问题。 看下面的例子。例如我们需要序列化的对象时ApparatusType,服务端的定义(使用了EntityFramework)是这样的:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using ProtoBuf;
namespace Hammergo.Data
{
[ProtoContract]
public partial class ApparatusType
{
public ApparatusType()
{
this.Apps = new List<App>();
}
[ProtoMember(1)]
public System.Guid Id { get; set; }
[ProtoMember(2)]
[MaxLength(20)]
public string TypeName { get; set; }
[ProtoIgnore]
public virtual ICollection<App> Apps { get; set; }
}
}
属于ProtoBuf 的三个修饰为
[ProtoContract]
[ProtoMember(1)]
[ProtoMember(2)]
其他的不用管,在客户端定义是这样的
using System;
using System.Collections.Generic;
using ProtoBuf;
namespace Hammergo.Poco
{
[ProtoContract]
public class ApparatusType
{
[ProtoMember(1)]
public virtual System.Guid Id { get; set; }
[ProtoMember(2)]
public virtual string TypeName { get; set; }
}
}
这里使用了Virtual关键字,是为了生成POCO的代理类,以跟踪状态,没有这个要求可以不使用。
如果使用https://github.com/damienbod/AspNetMvc6ProtobufFormatters 的方案就会发现
如果ASP.NET 的action返回List<AppratusType>,在客户端使用
var result =
response.Content.ReadAsAsync<List<Hammergo.Poco.ApparatusType>>(new[] { new ProtoBufFormatter() }).Result;
就会抛出异常,ReadAsAsync ProtoBuf Formatter No MediaTypeFormatter is available to read
大意是没有 相应的MediaTypeFormatter来供ReadAsAsync来使用,
检查https://github.com/damienbod/AspNetMvc6ProtobufFormatters 的方案,发现它调用了https://github.com/WebApiContrib/WebApiContrib.Formatting.ProtoBuf里面的ProtoBufFormatter.cs ,这个里面有一个错误。
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Reflection;
using System.Threading.Tasks;
using ProtoBuf;
using ProtoBuf.Meta;
namespace WebApiContrib.Formatting
{
public class ProtoBufFormatter : MediaTypeFormatter
{
private static readonly MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/x-protobuf");
private static Lazy<RuntimeTypeModel> model = new Lazy<RuntimeTypeModel>(CreateTypeModel);
public static RuntimeTypeModel Model
{
get { return model.Value; }
}
public ProtoBufFormatter()
{
SupportedMediaTypes.Add(mediaType);
}
public static MediaTypeHeaderValue DefaultMediaType
{
get { return mediaType; }
}
public override bool CanReadType(Type type)
{
return CanReadTypeCore(type);
}
public override bool CanWriteType(Type type)
{
return CanReadTypeCore(type);
}
public override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContent content, IFormatterLogger formatterLogger)
{
var tcs = new TaskCompletionSource<object>();
try
{
object result = Model.Deserialize(stream, null, type);
tcs.SetResult(result);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
return tcs.Task;
}
public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext)
{
var tcs = new TaskCompletionSource<object>();
try
{
Model.Serialize(stream, value);
tcs.SetResult(null);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
return tcs.Task;
}
private static RuntimeTypeModel CreateTypeModel()
{
var typeModel = TypeModel.Create();
typeModel.UseImplicitZeroDefaults = false;
return typeModel;
}
private static bool CanReadTypeCore(Type type)
{
return type.GetCustomAttributes(typeof(ProtoContractAttribute)).Any();
}
}
}
private static bool CanReadTypeCore(Type type)这个有问题,它只能识别有ProtoContract的类,没法识别其对应的IEnumerable<T>,修改这个方法就可以了。如下:
private static bool CanReadTypeCore(Type type)
{
bool isCan = type.GetCustomAttributes(typeof(ProtoContractAttribute)).Any();
if (!isCan && typeof(IEnumerable).IsAssignableFrom(type))
{
var temp = type.GetGenericArguments().FirstOrDefault();
isCan = temp.GetCustomAttributes(typeof(ProtoContractAttribute)).Any();
}
return isCan;
}
下面我给出,关键的代码片段:
使用了一个辅助Library,结构如下图:
DateTimeOffsetSurrogate.cs的代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ProtoBuf;
namespace ProtoBufHelper
{
[ProtoContract]
public class DateTimeOffsetSurrogate
{
[ProtoMember(1)]
public long DateTimeTicks { get; set; }
[ProtoMember(2)]
public short OffsetMinutes { get; set; }
public static implicit operator DateTimeOffsetSurrogate(DateTimeOffset value)
{
return new DateTimeOffsetSurrogate
{
DateTimeTicks = value.Ticks,
OffsetMinutes = (short)value.Offset.TotalMinutes
};
}
public static implicit operator DateTimeOffset(DateTimeOffsetSurrogate value)
{
return new DateTimeOffset(value.DateTimeTicks, TimeSpan.FromMinutes(value.OffsetMinutes));
}
}
}
ProtoBufFormatter.cs 的代码如下:
using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Reflection;
using System.Threading.Tasks;
using ProtoBuf;
using ProtoBuf.Meta;
namespace ProtoBufHelper
{
public class ProtoBufFormatter : MediaTypeFormatter
{
private static readonly MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/x-protobuf");
private static Lazy<RuntimeTypeModel> model = new Lazy<RuntimeTypeModel>(CreateTypeModel);
public static RuntimeTypeModel Model
{
get { return model.Value; }
}
public ProtoBufFormatter()
{
SupportedMediaTypes.Add(mediaType);
}
public static MediaTypeHeaderValue DefaultMediaType
{
get { return mediaType; }
}
public override bool CanReadType(Type type)
{
var temp = CanReadTypeCore(type);
return temp;
}
public override bool CanWriteType(Type type)
{
return CanReadTypeCore(type);
}
public override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContent content, IFormatterLogger formatterLogger)
{
var tcs = new TaskCompletionSource<object>();
try
{
object result = Model.Deserialize(stream, null, type);
tcs.SetResult(result);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
return tcs.Task;
}
public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext)
{
var tcs = new TaskCompletionSource<object>();
try
{
Model.Serialize(stream, value);
tcs.SetResult(null);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
return tcs.Task;
}
private static RuntimeTypeModel CreateTypeModel()
{
var typeModel = TypeModel.Create();
typeModel.UseImplicitZeroDefaults = false;
typeModel.Add(typeof(DateTimeOffset), false).SetSurrogate(typeof(DateTimeOffsetSurrogate));
return typeModel;
}
private static bool CanReadTypeCore(Type type)
{
bool isCan = type.GetCustomAttributes(typeof(ProtoContractAttribute)).Any();
if (!isCan && typeof(IEnumerable).IsAssignableFrom(type))
{
var temp = type.GetGenericArguments().FirstOrDefault();
isCan = temp.GetCustomAttributes(typeof(ProtoContractAttribute)).Any();
}
return isCan;
}
}
}
这样就可以设置ASP.NET Core端的代码:
添加ProtobufInputFormatter.cs 和 ProtobufOutputFormatter.cs 代码分别如下:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
using ProtoBuf.Meta;
using ProtoBufHelper;
namespace DamService
{
public class ProtobufInputFormatter : InputFormatter
{
private static Lazy<RuntimeTypeModel> model = new Lazy<RuntimeTypeModel>(CreateTypeModel);
public static RuntimeTypeModel Model
{
get { return model.Value; }
}
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var type = context.ModelType;
var request = context.HttpContext.Request;
MediaTypeHeaderValue requestContentType = null;
MediaTypeHeaderValue.TryParse(request.ContentType, out requestContentType);
object result = Model.Deserialize(context.HttpContext.Request.Body, null, type);
return InputFormatterResult.SuccessAsync(result);
}
public override bool CanRead(InputFormatterContext context)
{
return true;
}
private static RuntimeTypeModel CreateTypeModel()
{
var typeModel = TypeModel.Create();
typeModel.UseImplicitZeroDefaults = false;
typeModel.Add(typeof(DateTimeOffset), false).SetSurrogate(typeof(DateTimeOffsetSurrogate));
return typeModel;
}
}
}
using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
using ProtoBuf.Meta;
using ProtoBufHelper;
namespace DamService
{
public class ProtobufOutputFormatter : OutputFormatter
{
private static Lazy<RuntimeTypeModel> model = new Lazy<RuntimeTypeModel>(CreateTypeModel);
public string ContentType { get; private set; }
public static RuntimeTypeModel Model
{
get { return model.Value; }
}
public ProtobufOutputFormatter()
{
ContentType = "application/x-protobuf";
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/x-protobuf"));
//SupportedEncodings.Add(Encoding.GetEncoding("utf-8"));
}
private static RuntimeTypeModel CreateTypeModel()
{
var typeModel = TypeModel.Create();
typeModel.UseImplicitZeroDefaults = false;
typeModel.Add(typeof(DateTimeOffset), false).SetSurrogate(typeof(DateTimeOffsetSurrogate));
return typeModel;
}
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
{
var response = context.HttpContext.Response;
Model.Serialize(response.Body, context.Object);
return Task.FromResult(response);
}
}
}
在Startup.cs中
稿源:勤快学QKXue.NET
扩展阅读:
ASP.NET Core 使用protobuf(上)
http://qkxue.net/info/22913/ASP-NET-Core-protobuf
ASP.NET Core 使用protobuf(下)
http://qkxue.net/info/22914/ASP-NET-Core-protobuf