前言

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

又接无上老板的一个需求,需要让.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. H3C 端口绑定典型配置举例

  2. linux 内核定时器

    无论何时你需要调度一个动作以后发生, 而不阻塞当前进程直到到时, 内核定时器是给你 的工具. 这些定时器用来调度一个函数在将来一个特定的时间执行, 基于时钟嘀哒, 并且 可用作各类任务; 例如, 当硬 ...

  3. vueX中使用namespaced

    用法: namespaced:true; getter调用时: this.$store.getters['XXX/getXXX']; commit调用时: this.$store.commit('XX ...

  4. ASP.NET MVC4.0+EF+LINQ+bui+bootstrap+网站+角色权限管理系统(4)

    接下来就是菜单管理了,菜单分为两部分,一部分是菜单管理,另一部分是左边的树形菜单 数据库添加菜单表Menus USE [MVCSystem] GO /****** Object: Table [dbo ...

  5. CentOS 7 修改root密码

    1.开机,在启动菜单上选择CentOS Linux (3.10**.**.x86**) 7 (Core) 按下e,进入编辑模式2.将光标一直移动到 LANG=en_US.UTF-8 后面(如果找不到, ...

  6. JMeter FTP测试计划

    为了演示测试目的,我们将使用公共可用的FTP位置,可以使用它来测试文件的下载. 您可以使用市场上现有的任何可用的演示FTP位置.我们使用URL下的FTP位置: https://dlptest.com/ ...

  7. 超简单本地mock假数据测试,模拟后台数据返回必杀技

    温馨提示:急性子可以直接拉到最后观看方法步骤. 什么是mock? mock就是在开发过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试开发的方法. 使用mock有什么好处? ...

  8. WWDC2018 之 高性能 Auto Layout

    1. 关于 Auto Layout 的历史渊源 上世纪 90 年代,名叫 Cassowary的布局算法,通过将布局问题抽象成线性不等式,并分解成多个位置间的约束,解决了用户界面的布局问题. Apple ...

  9. mysql主从之配置基本环境

    实验环境 master  192.168.132.121 主库 slave     192.168.132.122 从库 一 mysql的使用介绍 1.1 mysql单台服务器特点 缺点 单台服务器如 ...

  10. $ [Contest \#4]$求和 思博题

    正解: 解题报告: 传送门$QwQ$ 一道看起来是数位$dp$其实并不是的题$QwQ$ 首先求$\sum_{l}^r$就变成$\sum_1^r-\sum_1^{l-1}$不说$QwQ$.现在就只要求$ ...