前言

**第二次发博客,希望大家多多鼓励!!! **

又接无上老板的一个需求,需要让.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. 1.4. 为现有的应用程序添加 Core Data 支持(Core Data 应用程序实践指南)

    项目创建时会有 “Use Core Data" ,但是,有时没有勾选这个选项,那么就要手动链接Core Data Framework. 选中 Grocery Dude Target Gene ...

  2. spring拦截器中修改响应消息头

    问题描述 前后端分离的项目,前端使用Vue,后端使用Spring MVC. 显然,需要解决浏览器跨域访问数据限制的问题,在此使用CROS协议解决. 由于该项目我在中期加入的,主要负责集成shiro框架 ...

  3. 【无私分享:ASP.NET CORE 项目实战(第二章)】添加EF上下文对象,添加接口、实现类以及无处不在的依赖注入(DI)

    目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 上一章,我们介绍了安装和新建控制器.视图,这一章我们来创建个数据模型,并且添加接口和实现类. 添加EF上下文对象 按照我们以前 ...

  4. [C#] .NET Core项目修改project.json来引用其他目录下的源码等文件的办法 & 解决多框架时 project.json 与 app.config冲突的问题

    作者: zyl910 一.缘由 项目规模大了后,经常会出现源码文件分布在不同目录的情况,但.NET Core项目默认只有项目目录下的源码文件,且不支持"Add As Link"方式 ...

  5. .NET Core项目修改project.json来引用其他目录下的源码等文件的办法 & 解决多框架时 project.json 与 app.config冲突的问题

    作者: zyl910 一.缘由 项目规模大了后,经常会出现源码文件分布在不同目录的情况,但.NET Core项目默认只有项目目录下的源码文件,且不支持“Add As Link”方式引入文件.这时需要手 ...

  6. Github上优秀的.NET Core项目

    Github上优秀的.NET Core开源项目的集合.内容包括:库.工具.框架.模板引擎.身份认证.数据库.ORM框架.图片处理.文本处理.机器学习.日志.代码分析.教程等. Github地址:htt ...

  7. 【转载】Github上优秀的.NET Core项目

    Github上优秀的.NET Core项目 Github上优秀的.NET Core开源项目的集合.内容包括:库.工具.框架.模板引擎.身份认证.数据库.ORM框架.图片处理.文本处理.机器学习.日志. ...

  8. Asp.Net Core 项目实战之权限管理系统(7) 组织机构、角色、用户权限

    0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...

  9. 【.NET Core项目实战-统一认证平台】第十五章 网关篇-使用二级缓存提升性能

    [.NET Core项目实战-统一认证平台]开篇及目录索引 一.背景 首先说声抱歉,可能是因为假期综合症(其实就是因为懒哈)的原因,已经很长时间没更新博客了,现在也调整的差不多了,准备还是以每周1-2 ...

随机推荐

  1. 基于串口调试助手的WIFI模块调试-FPGA简单联网(点灯)

    根据正点原子的<ATK-ESP8266 WIFI用户手册>,使用XCOM V2.2串口调试助手测试WIFI模块[26].在本系统中运用到的功能主要是TCP/IP模式中的TCP Client ...

  2. VMware虚拟机安装Windows2003操作教程

    1.下载好以下三个文件: 2.选择VMware安装包,跟随指令安装好后,打开: 3.选择"创建新的虚拟机"后,选择"安装光盘映像文件(iso)",点击浏览载入. ...

  3. vue-cli3 使用 svg-sprite-loader 的坑

    chainWebpack: config => { config.module.rules.delete("svg"); //重点:删除默认配置中处理svg, //const ...

  4. Linux 内核 动态设备

    术语"热插拔"最普遍使用的意义产生于当讨论这样的事实时, 几乎所有的计算机系统现在 能够处理当系统有电时设备的出现或消失. 这非常不同于只是几年前的计算机系统, 那时 程序员知道他 ...

  5. 2018-2-13-WPF-异常-NativeWPFDLLLoader.LoadNativeWPFDLL

    title author date CreateTime categories WPF 异常 NativeWPFDLLLoader.LoadNativeWPFDLL lindexi 2018-2-13 ...

  6. hdu 6579 Operation (在线线性基)

    传送门 •题意 一个数组a有n个数 m个操作 操作① 询问$[l,r]$区间的异或值 操作② 在数组末尾追加一个数x,数组长度变为$n+1$ 其中$l,r$不直接给出,其中$l=l%n+1,r=r%n ...

  7. dotnet 使用 System.CommandLine 写命令行程序

    在写命令行程序的时候,会遇到命令行解析的问题,以及参数的使用和规范化等坑.现在社区开源了命令行项目,可以帮助小伙伴快速开发命令行程序,支持自动的命令行解析和规范的参数 我写过一篇关于命令行解析的博客C ...

  8. HDU - 3530 Subsequence (单调队列)

    Subsequence Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total ...

  9. Team Foundation Server 2015使用教程【2】:默认团队成员添加

    官方文档:https://www.visualstudio.com/en-us/docs/setup-admin/add-users

  10. ML基础——搜索引擎与图书管理,百度与李彦宏

    本文始发于个人公众号:TechFlow 谈及机器学习,大家想必会有许多联想,比如最近火热的人工智能,再比如战胜李世石的AlphaGo,甚至还会有人联想起骇客帝国或者是机械公敌等经典机器人电影. 但实际 ...