修改现有消息类让.net core项目支持Protobuf - 【无需使用 [ProtoBuf.ProtoContract] 的方法】
前言
**第二次发博客,希望大家多多鼓励!!! **
又接无上老板的一个需求,需要让.net core消息发送端跟消息接收端通信的消息是protobuf格式的(基于protobuf比json小一倍数据量,独特的编码、没有fieldname等),但现有项目的消息类数量巨多,按照网上的方案是安装protobuf.net 这个nuget包,然后需要给消息类一个一个添加[ProtoBuf.ProtoContract]、[ProtoBuf.ProtoMember(index)]等Attributes,更可悲的是,还得处理继承的问题,也就是要有类似如下这种代码:
[ProtoContract]
[ProtoInclude(10, typeof(Male))]
public class Person
{
[ProtoMember(1)]
public int Id { get; set; }
[ProtoMember(2)]
public string Name { get; set; }
[ProtoMember(3)]
public Address Address { get; set;}
}
[ProtoContract]
public class Male : Person
{
}
[ProtoContract]
public class Address
{
[ProtoMember(1)]
public string Line1 {get;set;}
[ProtoMember(2)]
public string Line2 {get;set;}
}
关于为什么要设置上面这些attributes,跟protobuf的原理息息相关,有兴趣的朋友可以看看这篇文章,而关于protobuf.net的基本用法,可以参考这里
找解决方案,咱们不干体力活
对于项目存在巨多消息类,显然这么一个一个的加attributes既费时又容易出错。我拿着这个需求,怀着忐忑的心,一通操作,终于找到了想要的方案,也就是找到了without attributes的方法,顺便悄悄的告诉您,貌似国内还没谁发现这个方法 :
使用RuntimeTypeModel.Default进行类型及其Properties的配置
动动脑筋,上面的代码,如果不用attributes而是用RuntimeTypeModel.Default进行类型及其Properties的配置的话,代码就是的:
var personMetaType = RuntimeTypeModel.Default.Add(typeof (Person), false);
personMetaType.Add(1, "Id");
personMetaType.Add(2, "Name");
personMetaType.Add(3, "Address");
var addressMetaType = RuntimeTypeModel.Default.Add(typeof(Address), false);
addressMetaType.Add(1, "Line1");
addressMetaType.Add(2, "Line2");
// 给父类metaType添加子类型
personMetaType.AddSubType(10, typeof (Male));
// 然后添加子类型
RuntimeTypeModel.Default.Add(typeof(Male), false);
RuntimeTypeModel.Default.Add(typeof(Female), false);
但是仔细想想其实原理跟添加attributes是一个道理,
具体实现
有了上面这个方法,我们就会自然而然想到对所有消息类使用RuntimeTypeModel.Default进行类型及其Properties的配置,但我们又不可能费时费力的给项目的每个消息实体类添加这些代码,那么这里就想到了使用反射找出项目中所有消息实体类,然后一个一个的操作
先看看我们的消息基类:
/// <summary>
/// 使用MQ队列的消息基类
/// </summary>
public class MsgBase
{
/// <summary>
/// 消息编码、接入系统编码
/// </summary>
public string MessageCode { get; set; }
/// <summary>
/// 消息类型 (业务相关的一个枚举)
/// </summary>
public MessageTypeCode MessageType { get; set; }
}
很简单吧,然后看看我们给类动态添加“[ProtoBuf.*]”这些attributes的核心代码:
static bool isInit = false; // 避免重复初始化
/// <summary>
/// 初始化,消息发送跟处理程序在启动后就需要调用
/// </summary>
public static void Init()
{
if (!isInit)
{
var msgAssemblyName = "Msg Model 所在的 assemly long name";
// 需要处理MsgBase本身跟继承它的所有消息类型
var msgTypes = (from t in Assembly.Load(msgAssemblyName).GetTypes()
where (t.BaseType == typeof(MsgBase) || t.Name == "MsgBase")
select t).OrderBy(t=>t.Name).ToList();
foreach (var msgType in msgTypes)
{
AddTypeToModel(msgType, RuntimeTypeModel.Default);
}
isInit = true;
}
}
/// <summary>
/// 添加类型以及字段到模型中
/// </summary>
/// <param name="type"></param>
/// <param name="typeModel"></param>
/// <returns></returns>
private static void AddTypeToModel(Type type, RuntimeTypeModel typeModel)
{
if (typeModel.IsDefined(type))
{
return;
}
typeModel.IncludeDateTimeKind = true;
// 1. 进行类型配置
var metaType = typeModel.Add(type, true);
// Protobuf的顺序很重要,在序列化跟反序列化都需要保持一致的顺序,否则反序列化的时候就会出错
var publicProperties = type.GetProperties().Where(h => h.SetMethod != null).OrderBy(h => h.Name);
var complexPropertiesInfo = publicProperties.Where(f => !IsSimpleType(f.PropertyType)).OrderBy(h=>h.Name);
// 2. 进行此类型的Properties的配置
foreach (var simplePropertyInfo in publicProperties)
{
metaType.Add(simplePropertyInfo.Name);
}
// 复杂类型需要处理里面的每个简单类型,使用了递归操作
foreach (var complexPropertyInfo in complexPropertiesInfo)
{
if (complexPropertyInfo.PropertyType.IsGenericType)
{
// Protobuf的顺序很重要,在序列化跟反序列化都需要保持一致的顺序,否则反序列化的时候就会出错
foreach (var genericArgumentType in complexPropertyInfo.PropertyType.GetGenericArguments().OrderBy(h=>h.Name))
{
if (!IsSimpleType(genericArgumentType))
{
AddTypeToModel(genericArgumentType, typeModel);
}
}
}
else
{
AddTypeToModel(complexPropertyInfo.PropertyType, typeModel);
}
}
}
/// <summary>
/// 是否为简单类型
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private static bool IsSimpleType(Type type)
{
var underlyingType = Nullable.GetUnderlyingType(type);
var newType = underlyingType ?? type;
var simpleTypes = new List<Type>
{
typeof(byte),
typeof(sbyte),
typeof(short),
typeof(ushort),
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(float),
typeof(double),
typeof(decimal),
typeof(bool),
typeof(string),
typeof(char),
typeof(Guid),
typeof(DateTime),
typeof(DateTimeOffset),
typeof(byte[]),
typeof(string[])
};
return simpleTypes.Contains(newType) || newType.GetTypeInfo().IsEnum;
}
其实上面就是所有代码了,使用的话,就是在消息发送跟消息接收程序启动后,就调用上面的Init方法,仅需要调用一次额。当然聪明的你,肯定已经想到将它封装成一个工具类了,哈哈。
注意事项
细心的朋友可以注意到,我并没有调用AddSubType(其实我消息类的某些property确实是复杂类型且有父子关系的)以及可能你也发现了在上面的“想办法解决,咱们不干体力活”章节中父子类型注册到RuntimeTypeModel中有一个先后顺序,但上面的代码在实际使用过程中也就是消息接收端反序列化protobuf消息时并没出现问题。如果你的项目使用了上面的代码,结果发现反序列化不了,特别是抛了不能识别类型的错误,那么很可能就是我所说的两点要处理下。
希望大家多多评论,2020年身体健康,过得顺心!!!
修改现有消息类让.net core项目支持Protobuf - 【无需使用 [ProtoBuf.ProtoContract] 的方法】的更多相关文章
- 1.4. 为现有的应用程序添加 Core Data 支持(Core Data 应用程序实践指南)
项目创建时会有 “Use Core Data" ,但是,有时没有勾选这个选项,那么就要手动链接Core Data Framework. 选中 Grocery Dude Target Gene ...
- spring拦截器中修改响应消息头
问题描述 前后端分离的项目,前端使用Vue,后端使用Spring MVC. 显然,需要解决浏览器跨域访问数据限制的问题,在此使用CROS协议解决. 由于该项目我在中期加入的,主要负责集成shiro框架 ...
- 【无私分享:ASP.NET CORE 项目实战(第二章)】添加EF上下文对象,添加接口、实现类以及无处不在的依赖注入(DI)
目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 上一章,我们介绍了安装和新建控制器.视图,这一章我们来创建个数据模型,并且添加接口和实现类. 添加EF上下文对象 按照我们以前 ...
- [C#] .NET Core项目修改project.json来引用其他目录下的源码等文件的办法 & 解决多框架时 project.json 与 app.config冲突的问题
作者: zyl910 一.缘由 项目规模大了后,经常会出现源码文件分布在不同目录的情况,但.NET Core项目默认只有项目目录下的源码文件,且不支持"Add As Link"方式 ...
- .NET Core项目修改project.json来引用其他目录下的源码等文件的办法 & 解决多框架时 project.json 与 app.config冲突的问题
作者: zyl910 一.缘由 项目规模大了后,经常会出现源码文件分布在不同目录的情况,但.NET Core项目默认只有项目目录下的源码文件,且不支持“Add As Link”方式引入文件.这时需要手 ...
- Github上优秀的.NET Core项目
Github上优秀的.NET Core开源项目的集合.内容包括:库.工具.框架.模板引擎.身份认证.数据库.ORM框架.图片处理.文本处理.机器学习.日志.代码分析.教程等. Github地址:htt ...
- 【转载】Github上优秀的.NET Core项目
Github上优秀的.NET Core项目 Github上优秀的.NET Core开源项目的集合.内容包括:库.工具.框架.模板引擎.身份认证.数据库.ORM框架.图片处理.文本处理.机器学习.日志. ...
- Asp.Net Core 项目实战之权限管理系统(7) 组织机构、角色、用户权限
0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...
- 【.NET Core项目实战-统一认证平台】第十五章 网关篇-使用二级缓存提升性能
[.NET Core项目实战-统一认证平台]开篇及目录索引 一.背景 首先说声抱歉,可能是因为假期综合症(其实就是因为懒哈)的原因,已经很长时间没更新博客了,现在也调整的差不多了,准备还是以每周1-2 ...
随机推荐
- 基于串口调试助手的WIFI模块调试-FPGA简单联网(点灯)
根据正点原子的<ATK-ESP8266 WIFI用户手册>,使用XCOM V2.2串口调试助手测试WIFI模块[26].在本系统中运用到的功能主要是TCP/IP模式中的TCP Client ...
- VMware虚拟机安装Windows2003操作教程
1.下载好以下三个文件: 2.选择VMware安装包,跟随指令安装好后,打开: 3.选择"创建新的虚拟机"后,选择"安装光盘映像文件(iso)",点击浏览载入. ...
- vue-cli3 使用 svg-sprite-loader 的坑
chainWebpack: config => { config.module.rules.delete("svg"); //重点:删除默认配置中处理svg, //const ...
- Linux 内核 动态设备
术语"热插拔"最普遍使用的意义产生于当讨论这样的事实时, 几乎所有的计算机系统现在 能够处理当系统有电时设备的出现或消失. 这非常不同于只是几年前的计算机系统, 那时 程序员知道他 ...
- 2018-2-13-WPF-异常-NativeWPFDLLLoader.LoadNativeWPFDLL
title author date CreateTime categories WPF 异常 NativeWPFDLLLoader.LoadNativeWPFDLL lindexi 2018-2-13 ...
- hdu 6579 Operation (在线线性基)
传送门 •题意 一个数组a有n个数 m个操作 操作① 询问$[l,r]$区间的异或值 操作② 在数组末尾追加一个数x,数组长度变为$n+1$ 其中$l,r$不直接给出,其中$l=l%n+1,r=r%n ...
- dotnet 使用 System.CommandLine 写命令行程序
在写命令行程序的时候,会遇到命令行解析的问题,以及参数的使用和规范化等坑.现在社区开源了命令行项目,可以帮助小伙伴快速开发命令行程序,支持自动的命令行解析和规范的参数 我写过一篇关于命令行解析的博客C ...
- HDU - 3530 Subsequence (单调队列)
Subsequence Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total ...
- Team Foundation Server 2015使用教程【2】:默认团队成员添加
官方文档:https://www.visualstudio.com/en-us/docs/setup-admin/add-users
- ML基础——搜索引擎与图书管理,百度与李彦宏
本文始发于个人公众号:TechFlow 谈及机器学习,大家想必会有许多联想,比如最近火热的人工智能,再比如战胜李世石的AlphaGo,甚至还会有人联想起骇客帝国或者是机械公敌等经典机器人电影. 但实际 ...