在一些性能要求很高的应用中,使用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()]
public System.Guid Id { get; set; } [ProtoMember()]
[MaxLength()]
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()]
public long DateTimeTicks { get; set; }
[ProtoMember()]
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中

public void ConfigureServices(IServiceCollection services) 方法中这样添加MVC中间件

services.AddMvc(options =>
{
options.InputFormatters.Add(new ProtobufInputFormatter());
options.OutputFormatters.Add(new ProtobufOutputFormatter());
options.FormatterMappings.SetMediaTypeMappingForFormat("protobuf", MediaTypeHeaderValue.Parse("application/x-protobuf"));
});

整个Startup.cs代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using DamService.Data;
using DamService.Models;
using DamService.Services;
using System.Net;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.Net.Http.Headers; namespace DamService
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true); if (env.IsDevelopment())
{
// For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
builder.AddUserSecrets();
} builder.AddEnvironmentVariables();
Configuration = builder.Build();
} public IConfigurationRoot Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddScoped(provider => new Hammergo.Data.DamWCFContext(Configuration.GetConnectionString("OdataDBConnectionString"))); services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders(); services.AddMvc(options =>
{
options.InputFormatters.Add(new ProtobufInputFormatter());
options.OutputFormatters.Add(new ProtobufOutputFormatter());
options.FormatterMappings.SetMediaTypeMappingForFormat("protobuf", MediaTypeHeaderValue.Parse("application/x-protobuf"));
}); // Add application services.
//services.AddTransient<IEmailSender, AuthMessageSender>();
//services.AddTransient<ISmsSender, AuthMessageSender>();
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug(); //if (env.IsDevelopment())
//{
// app.UseDeveloperExceptionPage();
// app.UseDatabaseErrorPage();
// app.UseBrowserLink();
//}
//else
//{
// app.UseExceptionHandler("/Home/Error");
//} app.UseExceptionHandler(_exceptionHandler); app.UseStaticFiles(); app.UseIdentity(); // Add external authentication middleware below. To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715 app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
} private void _exceptionHandler(IApplicationBuilder builder)
{
builder.Run(
async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.ContentType = "text/plain"; var error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null)
{
await context.Response.WriteAsync(error.Error.Message).ConfigureAwait(false);
}
});
}
}
}

  上面的

services.AddScoped(provider => new Hammergo.Data.DamWCFContext(Configuration.GetConnectionString("OdataDBConnectionString")));是我自己的数据库连接,可以使用自己的,也可以不用,我用的是EntityFrameWork 6.1.3 不是core,目前core还有一些功能没有,暂时不使用。
 
 添加一个测试用的Controller,
using System;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Hammergo.Data;
using System.Linq.Expressions;
using System.Data.Entity;
using System.Collections.Generic;
using System.Threading.Tasks; namespace DamService.Controllers
{
public class AppsController : Controller
{
private readonly DamWCFContext _dbContext;
private readonly ILogger _logger; public AppsController(
DamWCFContext dbContext,
ILoggerFactory loggerFactory)
{
_dbContext = dbContext;
_logger = loggerFactory.CreateLogger<AccountController>();
} [HttpGet]
public async Task<List<App>> Top10()
{
return await _dbContext.Apps.Take(10).ToListAsync();
} }
}
客户端测试代码:
            var client = new HttpClient { BaseAddress = new Uri("http://localhost.Fiddler:40786/") };
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-protobuf")); HttpResponseMessage response = null; //test top 10
string uri = "Apps/Top10";
Trace.WriteLine("\n Test {0}.", uri);
response = client.GetAsync(uri).Result; if (response.IsSuccessStatusCode)
{
var result = response.Content.ReadAsAsync<List<Hammergo.Poco.App>>(new[] { new ProtoBufFormatter() }).Result; Assert.AreEqual(result.Count, 10, "反序列化失败"); Console.WriteLine("{0} test success!", uri);
}
else
{
var message = response.Content.ReadAsStringAsync().Result;
Console.WriteLine("{0} ({1})\n message: {2} ", (int)response.StatusCode, response.ReasonPhrase, message);
}

  

  

http://localhost.Fiddler:40786/  这里40786为服务端口,Fiddler表示使用了Fiddler代理,这样在使用时需要开启Fiddler,如果不使用Fidller,将URI修改为:
http://localhost:40786/
 
 

asp.net core 使用protobuf的更多相关文章

  1. 插上腾飞的翅膀:为asp.net core添加protobuf支持

    没时间解释了,快上车. 通过NuGet获取Zaabee.AspNetCoreProtobuf Install-Package Zaabee.AspNetCoreProtobuf 在Startup.cs ...

  2. ASP.NET Core 使用 Redis 和 Protobuf 进行 Session 缓存

    前言 上篇博文介绍了怎么样在 asp.net core 中使用中间件,以及如何自定义中间件.项目中刚好也用到了Redis,所以本篇就介绍下怎么样在 asp.net core 中使用 Redis 进行资 ...

  3. asp.net core 使用 Redis 和 Protobuf

    asp.net core 使用 Redis 和 Protobuf 前言 上篇博文介绍了怎么样在 asp.net core 中使用中间件,以及如何自定义中间件.项目中刚好也用到了Redis,所以本篇就介 ...

  4. 《ASP.NET Core跨平台开发从入门到实战》Web API自定义格式化protobuf

    <ASP.NET Core跨平台开发从入门到实战>样章节 Web API自定义格式化protobuf. 样章 Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于 ...

  5. ASP.NET Core 缓存技术 及 Nginx 缓存配置

    前言 在Asp.Net Core Nginx部署一文中,主要是讲述的如何利用Nginx来实现应用程序的部署,使用Nginx来部署主要有两大好处,第一是利用Nginx的负载均衡功能,第二是使用Nginx ...

  6. ASP.NET Core 文件上传

    前言 上篇博文介绍了怎么样在 asp.net core 使用 Redis 和 Protobuf 进行 Session缓存.本篇的是开发过程中使用的一个小功能,怎么做单文件和多文件上传. 如果你觉得对你 ...

  7. asp.net core 3.0 gRPC框架小试

    什么是gRPC gRPC是google开源的一个高性能.跨语言的RPC框架,基于HTTP2协议,采用ProtoBuf 定义的IDL. gRPC 的主要优点是: 现代高性能轻量级 RPC 框架. 协定优 ...

  8. ASP.NET Core 3.0 上的gRPC服务模板初体验(多图)

    早就听说ASP.NET Core 3.0中引入了gRPC的服务模板,正好趁着家里电脑刚做了新系统,然后装了VS2019的功夫来体验一把.同时记录体验的过程.如果你也想按照本文的步骤体验的话,那你得先安 ...

  9. 【分享】Asp.net Core相关教程及开源项目

    入门 全新的ASP.NET:  https://www.cnblogs.com/Leo_wl/p/5654828.html 在IIS上部署你的ASP.NET Core项目: https://www.c ...

随机推荐

  1. 循序渐进Python3(十一) --4--  web之jQuery

    jQuery         jQuery是一个快速.简洁的JavaScript框架,是继Prototype之后又一个优秀的JavaScript代码库(或JavaScript框架).jQuery设计的 ...

  2. android Activity绑定Service

    activity可以绑定Service,并且可以调用Service中定义的方法 Service代码:在里面多了一个IBinder;个人理解是用来与Activity绑定的主要通道: public cla ...

  3. Dev WPF使用总结

    1.换肤 ThemeManager.ApplicationThemeName = Theme.DXStyle.Name this.UpdateLayout(); //重新布局

  4. mmap和shm共享内存的区别和联系

    共享内存的创建 根据理论: 1. 共享内存允许两个或多个进程共享一给定的存储区,因为数据不需要来回复制,所以是最快的一种进程间通信机制.共享内存可以通过mmap()映射普通文件(特殊情况下还可以采用匿 ...

  5. B. Shaass and Bookshelf DP

    http://codeforces.com/contest/294/problem/B 据说是贪心,我用了一个复杂度是2e8的dp水过去了. 其实这题就是给你n个数,每个数有两个权值,分成两组,使得第 ...

  6. java_jdk_JDK版本切换批处理脚本

    我们平时在window上做开发的时候,可能需要同时开发两个甚至多个项目,有时不同的项目对JDK的版本要求有区别,这时候我们可能会在一台电脑上安装多个版本的JDK,如下图所示:

  7. 关于yii2框架活动记录activeRecord添加默认字段的问题

    平时使用sql的时候可以如下添加默认字段flag: "select a.*,0 as flag from user_info a", 对于yii2框架则需要这样: $query = ...

  8. [题解]USACO 1.3 Wormholes

    Wormholes Farmer John's hobby of conducting high-energy physics experiments on weekends has backfire ...

  9. des加密解密——java加密,php解密

    最近在做项目中,遇到des加密解密的问题. 场景是安卓app端用des加密,php这边需要解密.之前没有接触过des这种加密解密算法,但想着肯定会有demo.因此百度,搜了代码来用.网上代码也是鱼龙混 ...

  10. Struts 2学习笔记——拦截器相关

    一.添加国际化支持 默认的struts-deault.xml文件中已经定义了国际化拦截器,内容如下 <!-定义国际化拦截器--> <interceptor name="i1 ...