修改现有消息类让.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年身体健康,过得顺心!!!

原文地址:https://www.cnblogs.com/sutong/p/12222646.html

时间: 2024-11-08 22:35:30

修改现有消息类让.net core项目支持Protobuf - 【无需使用 [ProtoBuf.ProtoContract] 的方法】的相关文章

egit插件提交项目到github无需每次输入URL的方法

用eclipse的插件egit,向github push的时候,每次关闭并重新打开eclipse都要重新输入url和用户名,非常麻烦,在网上搜搜索了一通,在github群里问了一通,都没有较好的办法,这里又一个较为简单的方法 step1:首先由见项目,Team->Show In Repositories View step2:右击Remotes->Creates Remote 上面的红框输入你的github项目地址,项目地址可从你的项目主页面上获取,如下 这样,在每次提交的时候选择Config

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

问题描述 前后端分离的项目,前端使用Vue,后端使用Spring MVC. 显然,需要解决浏览器跨域访问数据限制的问题,在此使用CROS协议解决. 由于该项目我在中期加入的,主要负责集成shiro框架到项目中作为权限管理组件,之前别的同事已经写好了部分接口,我负责写一部分新的接口. 之前同事解决跨域问题使用Spring提供的@CrossOrigin注解: @RequestMapping(value = "/list.do", method = RequestMethod.GET) @R

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

目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 上一章,我们介绍了安装和新建控制器.视图,这一章我们来创建个数据模型,并且添加接口和实现类. 添加EF上下文对象 按照我们以前的习惯,我们还是新建几个文件夹 Commons:存放帮助类 Domians:数据模型 Services:接口和实现类 我们在Domains文件夹下添加一个类库 Domain 我们新建一个类 ApplicationDbContext 继承 DbContext 1 using Microsoft.Ent

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

作者: zyl910 一.缘由 项目规模大了后,经常会出现源码文件分布在不同目录的情况,但.NET Core项目默认只有项目目录下的源码文件,且不支持“Add As Link”方式引入文件.这时需要手工修改project.json文件了. 可能是因为最新版本已将 project.json 转为 .csproj,导致我花了一些功夫才找到配置办法,故写了这篇笔记. 二.引用其他目录下的源码等文件的办法 2.1 官网说明 官网的 project.json 和 csproj 属性之间的映射 里简单介绍了

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

0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之权限管理系统(2) 功能及实体设计 3 Asp.Net Core 项目实战之权限管理系统(3) 通过EntityFramework Core使用PostgreSQL 4 Asp.Net Core 项目实战之权限管理系统(4) 依赖注入.仓储.服务的多项目分层实现 5 Asp.Net Core 项目实

【.NET Core项目实战-统一认证平台】第十章 授权篇-客户端授权

原文 [.NET Core项目实战-统一认证平台]第十章 授权篇-客户端授权 [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章介绍了如何使用Dapper持久化IdentityServer4(以下简称ids4)的信息,并实现了sqlserver和mysql两种方式存储,本篇将介绍如何使用ids4进行客户端授权. .netcore项目实战交流群(637326624),有兴趣的朋友可以在群里交流讨论. 一.如何添加客户端授权? 在了解如何进行客户端授权时,我们需要了解详细的授权流程

【.NET Core项目实战-统一认证平台】第十二章 授权篇-深入理解JWT生成及验证流程

原文:[.NET Core项目实战-统一认证平台]第十二章 授权篇-深入理解JWT生成及验证流程 [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章介绍了基于Ids4密码授权模式,从使用场景.原理分析.自定义帐户体系集成完整的介绍了密码授权模式的内容,并最后给出了三个思考问题,本篇就针对第一个思考问题详细的讲解下Ids4是如何生成access_token的,如何验证access_token的有效性,最后我们使用.net webapi来实现一个外部接口(本来想用JAVA来实现的,

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

原文:[.NET Core项目实战-统一认证平台]第十五章 网关篇-使用二级缓存提升性能 [.NET Core项目实战-统一认证平台]开篇及目录索引 一.背景 首先说声抱歉,可能是因为假期综合症(其实就是因为懒哈)的原因,已经很长时间没更新博客了,现在也调整的差不多了,准备还是以每周1-2篇的进度来更新博客,并完成本项目所有功能. 言归正传,本重构项目是在我根据实际需求重构,由于还未完全写完,所以也没进行压测,在2月份时,张善友老师给我留言说经过压测发现我重构的Ocelot网关功能性能较差,其中

Asp.Net Core 项目实战之权限管理系统(6) 功能管理

0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之权限管理系统(2) 功能及实体设计 3 Asp.Net Core 项目实战之权限管理系统(3) 通过EntityFramework Core使用PostgreSQL 4 Asp.Net Core 项目实战之权限管理系统(4) 依赖注入.仓储.服务的多项目分层实现 5 Asp.Net Core 项目实