ASP.NET Core 使用protobuf

來源:互聯網
上載者:User

標籤:

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.