修改現有消息類讓.net core項目支持Protobuf - 【無需使用 [ProtoBuf.ProtoContract] 的方法】

##前言
**第二次發博客,但願你們多多鼓勵!!! **html

又接無上老闆的一個需求,須要讓.net core消息發送端跟消息接收端通訊的消息是protobuf格式的(基於protobuf比json小一倍數據量,獨特的編碼、沒有fieldname等),但現有項目的消息類數量巨多,按照網上的方案是安裝protobuf.net 這個nuget包,而後須要給消息類一個一個添加[ProtoBuf.ProtoContract]、[ProtoBuf.ProtoMember(index)]等Attributes,更可悲的是,還得處理繼承的問題,也就是要有相似以下這種代碼:git

[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的基本用法,能夠參考這裏github

###找解決方案,我們不幹體力活json

對於項目存在巨多消息類,顯然這麼一個一個的加attributes既費時又容易出錯。我拿着這個需求,懷着忐忑的心,一通操做,終於找到了想要的方案,也就是找到了without attributes的方法,順便悄悄的告訴您,貌似國內還沒誰發現這個方法c#

使用RuntimeTypeModel.Default進行類型及其Properties的配置工具

動動腦筋,上面的代碼,若是不用attributes而是用RuntimeTypeModel.Default進行類型及其Properties的配置的話,代碼就是的:ui

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是一個道理,編碼

###具體實現.net

有了上面這個方法,咱們就會天然而然想到對全部消息類使用RuntimeTypeModel.Default進行類型及其Properties的配置,但咱們又不可能費時費力的給項目的每一個消息實體類添加這些代碼,那麼這裏就想到了使用反射找出項目中全部消息實體類,而後一個一個的操做code

先看看咱們的消息基類:

/// <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

相關文章
相關標籤/搜索