首页 > 代码库 > ASP.NET Core 使用protobuf

ASP.NET Core 使用protobuf

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