【问题标题】:Protobuf net not serializing when using WCF使用 WCF 时 Protobuf 网络未序列化
【发布时间】:2012-04-11 23:45:04
【问题描述】:

我正在尝试使用 protobuf 序列化我的 WCF 调用,但似乎该对象没有被客户端序列化。需要注意的几点:

  • 我正在使用共享 DTO 库。
  • 我正在使用 ChannelFactory 来 调用服务(因此类型不会丢失它们的数据成员 属性)。
  • 我可以序列化和反序列化对象,只需使用 普通的 protobuf.net 代码,所以类型本身似乎没问题
  • 我正在使用 protobuf.net 的 2.0.0.480 版本
  • 我没有发布服务代码,因为问题出在外发消息上(消息日志贴在下面)
  • 如果我不使用 protobuf 端点行为,客户端和服务可以正常工作。

我的主要方法如下

static void Main(string[] args)
    {
        Base.PrepareMetaDataForSerialization();
        FactoryHelper.InitialiseFactoryHelper(new ServiceModule());
        Member m = new Member();
        m.FirstName = "Mike";
        m.LastName = "Hanrahan";
        m.UserId = Guid.NewGuid();
        m.AccountStatus = MemberAccountStatus.Blocked;
        m.EnteredBy = "qwertt";
        ChannelFactory<IMembershipService> factory = new ChannelFactory<IMembershipService>("NetTcpBinding_MembershipService");
        var client = factory.CreateChannel();

        using (var ms = new MemoryStream())
        {
            Serializer.Serialize<Member>(ms, m);
            Console.WriteLine(ms.Length.ToString());
            ms.Position = 0;
            var member2 = Serializer.Deserialize<Member>(ms);
            Console.WriteLine(member2.EnteredBy);
            Console.WriteLine(member2.FirstName);
        }

        var result = client.IsMemberAllowedToPurchase(m);

        System.Console.Write(result.IsValid.ToString());
        factory.Close();
        var input = Console.ReadLine();
    }

我的客户端配置如下:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
</configSections>
<system.serviceModel>
<bindings>
  <netTcpBinding>
    <binding name="NetTcpBinding_Common" closeTimeout="00:01:00"
      openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
      transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions"
      hostNameComparisonMode="StrongWildcard" listenBacklog="10" maxBufferPoolSize="524288"
      maxBufferSize="1000065536" maxConnections="10" maxReceivedMessageSize="1000000">
      <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
        maxBytesPerRead="4096" maxNameTableCharCount="16384" />
      <reliableSession ordered="true" inactivityTimeout="00:10:00"
        enabled="false" />
      <security mode="Transport">
        <transport clientCredentialType="Windows" protectionLevel="EncryptAndSign" />
        <message clientCredentialType="Windows" />
      </security>
    </binding>
  </netTcpBinding>
</bindings>
<behaviors>
  <endpointBehaviors>
    <behavior name="Proto.Common.EndpointBehavior">
      <protobuf />
    </behavior>
  </endpointBehaviors>
</behaviors>
<extensions>
  <behaviorExtensions>
    <add name="protobuf" type="ProtoBuf.ServiceModel.ProtoBehaviorExtension, protobuf-net, Version=2.0.0.480, Culture=neutral, PublicKeyToken=257b51d87d2e4d67" />
  </behaviorExtensions>
</extensions>
<client>
  <endpoint address="net.tcp://mikes-pc:12002/MembershipService.svc"
    behaviorConfiguration="Proto.Common.EndpointBehavior" binding="netTcpBinding"
    bindingConfiguration="NetTcpBinding_Common" contract="PricesForMe.Core.Entities.ServiceInterfaces.IMembershipService"
    name="NetTcpBinding_MembershipService">
    <identity>
      <userPrincipalName value="Mikes-PC\Mike" />
    </identity>
  </endpoint>
</client>
<diagnostics>
  <messageLogging
       logEntireMessage="true"
       logMalformedMessages="true"
       logMessagesAtServiceLevel="true"
       logMessagesAtTransportLevel="true"
       maxMessagesToLog="3000"
       maxSizeOfMessageToLog="2000"/>
</diagnostics>
</system.serviceModel>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />
</startup>
<system.diagnostics>
<sources>
  <source name="System.ServiceModel"
          switchValue="Information, ActivityTracing"
          propagateActivity="true">
    <listeners>
      <add name="traceListener"
          type="System.Diagnostics.XmlWriterTraceListener"
          initializeData="E:\Work\Logs\IMembershipServiceWcfTrace_Client.svclog"  />
    </listeners>
  </source>
  <source name="System.ServiceModel.MessageLogging">
    <listeners>
      <add name="messages"
      type="System.Diagnostics.XmlWriterTraceListener"
      initializeData="E:\Work\Logs\IMembershipServiceWcfTrace_Client_messages.svclog" />
    </listeners>
  </source>
</sources>
</system.diagnostics>
</configuration>

记录客户端消息后,我在日志中得到以下条目

    <MessageLogTraceRecord>
    <s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
    <s:Header>
    <a:Action s:mustUnderstand="1">http://www.pricesforme.com/services/MembershipService/IsMemberAllowedToPurchase</a:Action>
    <a:MessageID>urn:uuid:8b545576-c453-4be6-8d5c-9913e2cca4bf</a:MessageID>
    <ActivityId CorrelationId="b4e9361f-1fbc-4b2d-b7ee-fb493847998a" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">6d712899-62fd-4547-9517-e9de452305c6</ActivityId>
    <a:ReplyTo>
    <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
    </a:ReplyTo>
    <VsDebuggerCausalityData xmlns="http://schemas.microsoft.com/vstudio/diagnostics/servicemodelsink"></VsDebuggerCausalityData>
    </s:Header>
    <s:Body>
    <IsMemberAllowedToPurchase xmlns="http://www.pricesforme.com/services/">
    <proto></proto>
    </IsMemberAllowedToPurchase>
    </s:Body>
    </s:Envelope>
    </MessageLogTraceRecord>

从上面的日志消息中可以看出,proto 条目中没有数据。我的成员类如下所示:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Runtime.Serialization;
    using PricesForMe.Core.Entities.Common;
    using PricesForMe.Core.Entities.Ordering;

    namespace PricesForMe.Core.Entities.Members
    {
        /// <summary>
        /// This entity represents a member or user of the site.
        /// </summary>
        [DataContract]
        [Serializable]
        public class Member: User
        {
            public Member()
                :base()
            {
                EntityType = Entities.EntityType.Member;
            }

            [DataMember(Order = 20)]
            public int Id { get; set; }

            [DataMember(Order = 21)]
            public string MemberName { get; set; }

            [DataMember(Order = 22)]
            public PaymentInfo DefaultPaymentMethod { get; set; }

            [DataMember(Order = 23)]
            public MemberAccountStatus AccountStatus { get; set; }

            #region static

            public static readonly string CacheCollectionKey = "MemberCollection";

            private static readonly string CacheItemKeyPrefix = "Member:";

            public static string GetCacheItemKey(int id)
            {
                return CacheItemKeyPrefix + id.ToString();
            }

            #endregion
        }
    }

父用户类如下所示:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Runtime.Serialization;
    using System.Diagnostics.Contracts;

    namespace PricesForMe.Core.Entities.Common
    {
        /// <summary>
        /// This class represents a user in the system.  For example, a user could be a member or a merchant user.
        /// </summary>
        [DataContract]
        [Serializable]
        public class User: Base
        {
            public User()
                :base()
            {
                EntityType = Entities.EntityType.User;
            }

            [DataMember(Order = 10)]
            public Guid UserId { get; set; }

            [DataMember(Order = 11, Name = "First Name")]
            public string FirstName { get; set; }

            [DataMember(Order = 12, Name = "Last Name")]
            public string LastName { get; set; }

            }
        }
    }

而基类如下所示:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Runtime.Serialization;
    using System.Diagnostics.Contracts;
    using System.ComponentModel.DataAnnotations;
    using System.Reflection;
    using ProtoBuf.Meta;

    namespace PricesForMe.Core.Entities
    {
        /// <summary>
        /// This is the base class for all entities involved in the request/response pattern of our services
        /// </summary>
        /// <remarks>
        /// The objects derived from this class are used to transfer data from our service classes to our UIs and back again and they should 
        /// not contain any logic.
        /// </remarks>
        [DataContract]
        [Serializable]
        public abstract class Base
        {
            public Base()
            {
                //Set some defaults for this
                EnteredBy = System.Environment.UserName;
                EnteredSource = System.Environment.MachineName;
            }

            /// <summary>
            /// This is the record timestamp
            /// </summary>
            [DataMember(Order = 2)]
            public DateTime RecordTimeStamp { get; set; }

            /// <summary>
            /// This is the name of the user who last edited the entity
            /// </summary>
            [DataMember(Order = 3)]
            public string EnteredBy { get; set; }

            /// <summary>
            /// This is the source of the last edited entity
            /// </summary>
            [DataMember(Order = 4)]
            public string EnteredSource { get; set; }

            [DataMember(Order = 5)]
            private PricesForMe.Core.Entities.Common.ValidationResult _validationResult = null;
            /// <summary>
            /// Data on the validity of the entity.
            /// </summary>
            public PricesForMe.Core.Entities.Common.ValidationResult ValidationData
            {
                get
                {
                    _validationResult = Validate();
                    return _validationResult;
                }
                set
                {
                    _validationResult = value;
                }
            }

            /// <summary>
            /// Flag denoting if the record is a new record or not.
            /// </summary>
            /// <remarks>
            /// To flag an entity as an existing record call the "FlagAsExistingReport()" method.
            /// </remarks>
            public bool IsNewRecord
            {
                get
                {
                    return _isNewRecord;
                }
            }

            [DataMember(Order = 6)]
            protected bool _isNewRecord = true;
            /// <summary>
            /// Flags the entity as a record that already exists in the database
            /// </summary>
            /// <remarks>
            /// This is a method rather than a field to demonstrait that this should be called with caution (as opposed to inadvertantly setting a flag!)
            /// <para>
            /// Note that this method should only need to be called on object creation if the entity has a composite key.  Otherwise the flag is
            /// set when the id is being set.  It should always be called on saving an entity.
            /// </para>
            /// </remarks>
            public void FlagAsExistingRecord()
            {
                _isNewRecord = false;
            }

            public virtual PricesForMe.Core.Entities.Common.ValidationResult Validate()
            {
                if (_validationResult == null)
                {
                    _validationResult = new PricesForMe.Core.Entities.Common.ValidationResult();
                    _validationResult.MemberValidations = new List<Common.ValidationResult>();
                    _validationResult.RulesViolated = new List<Common.ValidationRule>();
                }
                return _validationResult;
            }

            /// <summary>
            /// This is the type of entity we are working with
            /// </summary>
            [DataMember(Order = 7)]
            private EntityType _entityType = EntityType.Unknown;
            public EntityType EntityType
            {
                get
                {
                    return _entityType;
                }
                protected set
                {
                    _entityType = value;
                }
            }


            /// <summary>
            /// Flag to say if the id generated for this class need to be int64 in size.
            /// </summary>
            [DataMember(Order = 9)]
            public bool IdRequiresInt64 { get; protected set; }

            /// <summary>
            /// This method tells us if the database id has been assigned.  Note that this does
            /// not mean the entity has been saved, only if the id has been assigned (so the id could be greater than 0, but the
            /// entity could still be a NewRecord
            /// </summary>
            /// <returns></returns>
            [DataMember(Order = 8)]
            public bool HasDbIdBeenAssigned { get; protected set; }

            private Guid _validationId = Guid.NewGuid();
            public Guid EntityValidationId
            {
                get
                {
                    return _validationId;
                }
            }

            /// <summary>
            /// Converts an object into another type of object based on the mapper class provided.
            /// </summary>
            /// <remarks>
            /// This method allows us to easily convert between objects without concerning ourselves with the mapping implementation.  This
            /// allows us to use various mapping frameworks (e.g. Automapper, ValueInjector) or create our own custom mapping.
            /// </remarks>
            /// <typeparam name="TDestination">The type we want to convert to</typeparam>
            /// <typeparam name="KMapper">The mapping type</typeparam>
            /// <returns>The new type</returns>
            public TDestination ConvertTo<TDestination, TSource, KMapper>()
                where KMapper : IEntityMapper<TDestination, TSource>
                where TSource : class
            {
                return Base.ConvertToItem<TDestination, TSource, KMapper>(this as TSource);
            }

            /// <summary>
            /// Returns all known child types
            /// </summary>
            public IEnumerable<Type> GetAllTypes()
            {
                Assembly current = Assembly.GetCallingAssembly();
                List<Type> derivedTypes = new List<Type>();
                var allTypes = current.GetTypes();
                foreach (var t in allTypes)
                {
                    if (t.IsAssignableFrom(typeof(Base)))
                    {
                        derivedTypes.Add(t);
                    }
                }
                return derivedTypes;
            }

            #region Static Methods
            /// <summary>
            /// Converts a list of one type to a list of another type
            /// </summary>
            /// <typeparam name="TDestination">The type we want to convert to</typeparam>
            /// <typeparam name="TSource">The source type</typeparam>
            /// <typeparam name="KMapper">The mapper class</typeparam>
            /// <param name="source">The source list of items.</param>
            /// <returns></returns>
            public static List<TDestination> ConvertToList<TDestination, TSource, KMapper>(IEnumerable<TSource> source)
                where KMapper : IEntityMapper<TDestination, TSource>
                where TSource : class
            {
                List<TDestination> result = new List<TDestination>();
                KMapper mapper = Activator.CreateInstance<KMapper>();
                foreach (var item in source)
                {
                    result.Add(mapper.Convert(item));
                }
                return result;
            }

            public static TDestination ConvertToItem<TDestination, TSource, KMapper>(TSource source)
                where KMapper : IEntityMapper<TDestination, TSource>
                where TSource : class
            {
                //Return default (i.e. null for ref objects) if the source is null.
                if (source == null) { return default(TDestination); }

                KMapper mapper = Activator.CreateInstance<KMapper>();
                return mapper.Convert(source);
            }


            private static object _metaLock = new object();
            private static bool _metaDataPrepared = false;
            /// <summary>
            /// Creates protobuf type models from the entities in this assembly
            /// </summary>
            public static void PrepareMetaDataForSerialization()
            {
                lock (_metaLock)
                {
                    if (_metaDataPrepared) { return; }

                    Assembly current = Assembly.GetExecutingAssembly();
                    var allTypes = current.GetTypes();
                    foreach (var t in allTypes)
                    {
                        checkType(t);
                    }
                }
            }

            private static void checkType(Type type)
            {
                Assembly current = Assembly.GetExecutingAssembly();
                var allTypes = current.GetTypes();
                int key = 1000;
                foreach (var t in allTypes)
                {
                    if (t.IsSubclassOf(type) && t.BaseType == type)
                    {
                        RuntimeTypeModel.Default[type].AddSubType(key, t);
                        key++;
                    }
                }
            }

            #endregion
        }
    }

base 上的PrepareMetaDataForSerialization 方法为protobuf.net 配置RuntimeModel,但我之前提到过,序列化和反序列化在WCF 之外工作正常,所以我认为DTO 是可以的。非常感谢有关可能导致问题的任何想法。

【问题讨论】:

    标签: wcf protobuf-net


    【解决方案1】:

    k;元素名称看起来是正确的(proto,匹配 XmlProtoSerializer.PROTO_ELEMENT),所以 protobuf-net 肯定会尝试做一些事情。它也不包括@nil 来代表null,所以它知道有数据。除此之外,它将对象序列化为MemoryStream 并将其写入为base-64(与byte[] 等的表示方式相同,这允许WCF 在启用MTOM 之类的东西时静默自动地提升数据)。所以问题变成了“为什么我的类型会序列化为空?”

    DataContract/DataMember 的使用很好,与我现有的 WCF 集成测试相匹配。

    我想知道其中有多少是由于继承(显示的成员中只有一个与具体类型相关,我大胆猜测Blocked0,它具有特殊处理)。

    然而,我不能足够强调您当前的继承处理是多么不安全;数字很​​重要,反射不能保证重新排序。我强烈建议您重新审视这一点,并使继承编号更可预测。

    非常小的观察,但不需要存储EntityType - 它完全是多余的,可以通过多态处理而不需要存储。

    另外,还有一个重要的错误是_metaDataPrepared 永远不会设置为 true。

    但是!最终我无法重现这个;我已使用您的代码(或大部分代码)生成an integration test,并且 - 它通过了;含义:使用 WCF、NetTcpBinding、您的类(包括您的继承修复代码)和 protobuf 打包,它可以正常工作。通过网络传输的数据是我们期望的数据。

    很高兴尝试进一步提供帮助,但我需要能够重现它......而现在,我不能。

    我要做的第一件事是添加缺少的_metaDataPrepared = true; 看看是否有帮助。

    【讨论】:

    • 感谢您的详细回复。我回到第一原则并创建了一个非常简单的类,没有继承,只有一个成员,但仍然遇到问题。然而,今天早上经过几个小时的努力,事实证明答案很简单。我在 IIS 中托管服务,而 IIS 没有选择端点行为配置(由于这篇文章 stackoverflow.com/questions/8243914/… 中概述的原因)。一旦我修复了一切正常。
    • 另外,消息日志显示空 proto 值的原因是我没有正确读取日志。每个请求的日志中有两个条目。一个具有“ServiceLevelSendRequest”源,其原始节点为空,但第二个具有“TransportSend”源,具有编码流。
    • 最后,感谢您发现 _metaDataPrepared 错误(这只是测试时的一种快速方法……实际方法将是私有的,并在基类的静态构造函数中调用一次)。 EntityType 被存储为原始的 DataContractor 反序列化不调用构造函数(可以实现它调用的另一种方法,但我更容易存储它)。关于继承编号……您对此有什么进一步的建议吗? (我在我的单元测试中进行了测试,以确保编号是唯一的……这就足够了吗?)。谢谢。
    • @Mike 除了唯一之外,客户端和服务器之间需要相同。反射通常不保证任何特定的顺序,如果您的客户端/服务器是单独版本控制的,它需要能够仅在一侧具有额外的类型。
    【解决方案2】:

    我认为您需要根据this SO 使用 ProtoContract 而不是 DataContract?此外,请确保在配置服务引用时设置“在引用的程序集中重用类型”。根据this SO,他们支持数据合同,但您必须设置订单 [DataMember(Order = 0)](至少这对我有用)。

    【讨论】:

    • TH OP 声明他们正在使用程序集共享,因此“重用类型”是不必要的。还有明确的Orders 集。
    • 谢谢,我已纠正。你在哪里设置 _metaDataPrepared = true?
    • 我链接到一个完整的例子,包括我的答案
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-09
    • 2010-10-15
    • 1970-01-01
    • 1970-01-01
    • 2019-04-26
    • 1970-01-01
    相关资源
    最近更新 更多