Protobuf网在使用WCF时没有序列化

时间:2012-04-11 23:45:05

标签: wcf protobuf-net

我正在尝试使用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
        }
    }

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

2 个答案:

答案 0 :(得分:2)

K表;元素名称看起来正确(proto,匹配XmlProtoSerializer.PROTO_ELEMENT),所以protobuf-net绝对试图做某事。它也不包含@nil来表示null,因此它知道有数据。除此之外,它将对象序列化为MemoryStream并将其写为base-64(与byte[]等的表示方式相同,这允许WCF在{{1}之类的情况下以静默方式自动提升数据已启用)。所以问题变成了“为什么我的类型会序列化为什么?”

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

我想知道这有多少是由于继承(显示的成员中只有一个与具体类型有关,我会猜测DataMemberBlocked,它具有特殊的处理能力)。

但是,我不能强调你当前的继承处理是多么不安全;数字很​​重要,反思不能保证重新排序。我强烈建议你重新审视这个并使继承编号更具可预测性。

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

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

然而!最终我无法重现这一点;我已经使用你的代码(或大部分代码)生成an integration test,并且 - 它通过了;意思是:使用WCF,_metaDataPrepared,你的类(包括你的继承修复代码)和protobuf打包,只是工作。通过网络传输的数据是我们期望的数据。

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

我要做的第一件事就是添加缺少的NetTcpBinding以查看是否有帮助。

答案 1 :(得分:0)

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