前言

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

又接无上老板的一个需求,需要让.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. 2018-10-19-C#-AddRange-添加位置

    title author date CreateTime categories C# AddRange 添加位置 lindexi 2018-10-19 9:3:8 +0800 2018-2-13 17 ...

  2. 【62.89%】【BZOJ 1072】[SCOI2007]排列perm

    Time Limit: 10 Sec  Memory Limit: 128 MB Submit: 1862  Solved: 1171 [Submit][Status][Discuss] Descri ...

  3. ZR提高失恋测4

    ZR提高失恋测4 比赛链接 A (方便讨论,设读入的串为\(S,T\)答案串为\(A\)) 首先\(*\)只会有一个 这是这道题目中非常重要的一个结论 简单证明一下? 因为\(*\)可以代表所有的字符 ...

  4. codeforces 1217E E. Sum Queries? (线段树

    codeforces 1217E E. Sum Queries? (线段树 传送门:https://codeforces.com/contest/1217/problem/E 题意: n个数,m次询问 ...

  5. apache WEB服务器安装(包括虚拟主机)

    一.apache下载编译安装 yum install apr apr-devel apr-util apr-util-devel gcc-c++ wget tar -y cd /usr/src wge ...

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

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

  7. 图解Go里面的互斥锁mutex了解编程语言核心实现源码

    1. 锁的基础概念 1.1 CAS与轮询 1.1.1 cas实现锁 在锁的实现中现在越来越多的采用CAS来进行,通过利用处理器的CAS指令来实现对给定变量的值交换来进行锁的获取 1.1.2 轮询锁 在 ...

  8. CodeForces - 1228D

    乍一看,嗯,图论题,不错: 结果,这尼玛是模拟???? 传送链接:https://codeforces.com/contest/1228/problem/D 看了大佬的代码瞬间就明白了许多!!! #i ...

  9. Elasticsearch系列---实战搜索语法

    概要 本篇介绍Query DSL的语法案例,查询语句的调试,以及排序的相关内容. 基本语法 空查询 最简单的搜索命令,不指定索引和类型的空搜索,它将返回集群下所有索引的所有文档(默认显示10条): G ...

  10. 如何学习Java基础

    Java是用于软件开发的最流行的编程语言,无论做自动化测试或者测试开发,Java依然是最重要的选项之一. 为什么要学习Java? Java很容易学习 Java是通用的,面向对象的,高性能,解释型,安全 ...