標籤:
ASP.NET Core 使用protobuf
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
ASP.NET Core 使用protobuf