2012-04-11 8 views
2

protobufを使用してWCF呼び出しをシリアル化しようとしていますが、オブジェクトがクライアントによってシリアル化されていないようです。メモの対象:ProtobufネットがWCFを使用してシリアル化しない

  • 私は共有DTOライブラリを使用しています。
  • 私はChannelFactoryを使用して サービスを呼び出しています(したがって、タイプはデータの 属性を失っていません)。
  • 私はシリアライズとデシリアライズオブジェクトをちょうど 通常protobuf.netコードを使用して、その種類自体が、私はprotobuf.net
  • のバージョン2.0.0.480を使用しています
  • OKのように見える私が投稿していないことができます問題のあるサービスコードは送信メッセージ(メッセージログは以下に掲載)にあります。
  • 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> 

したがって、abov eログメッセージには、プロトエントリにはデータがありません。

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 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; 
    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は問題ありません。問題の原因となる可能性のあるアイデアは非常に高く評価されます。

+0

調査中... –

答えて

2

k;要素名が正しく見えるので(proto、一致するXmlProtoSerializer.PROTO_ELEMENT)、protobuf-netは間違いなく何かをしようとしました。 nullを表す@nilも含まれていないため、データがあることがわかります。それ以外では、MemoryStreamにオブジェクトをシリアル化し、MTOMなどのようなものが有効な場合、WCFがデータをサイレントモードで自動的に持ち上げることを可能にする、byte[]などと同じベース64として書き込みます。だから問題は「なぜ私のタイプは何にもシリアライズしませんか?「

DataContract/DataMemberの使用は結構です、と私の既存のWCFの統合テストと一致する。

私が原因継承(具体的なタイプに関係示した部材の一つだけにどのくらいこの疑問に思います。数字の問題を、そして反射が順番再保証しません、私はだろう、と私はあなたの現在の継承の取り扱いはどのように危険な私は、しかし、十分に強調することはできませんBlockedが特定の取り扱いを持っている0、)であることを

を推測するハザードます。これを再訪し、継承の番号付けをより予測可能にすることを強くお勧めします。

非常に軽微な観察ですが、EntityTypeを保存する必要はありません。完全に冗長であり、保存の必要がない多形性で処理することができます。

また、_metaDataPreparedが決してtrueに設定されないという重要なバグがあります。

ただし、最終的に私はこれを再現することはできません。私はan integration testを生成するためにあなたのコード(またはそのほとんど)を使用しています。つまり、WCF、NetTcpBinding、クラス(継承フィックスアップコードを含む)、およびprotobufパッキングを使用すると、はちょうどで動作します。ワイヤを通過するデータは、私たちが期待するデータです。

さらに助けてもらいたいですが、私はそれを再現できるようにする必要があります...今は、できません。

最初に行うことは、不足している_metaDataPrepared = true;を追加して、それが役立つかどうかを確認することです。

+0

詳細な応答ありがとうございます。私は最初の原則に戻って、継承のないとてもシンプルなクラスを作成しました。ただ1人のメンバーで、まだ問題を抱えていました。しかし、今朝の努力の多くの時間の後、それは答えが何か簡単だったことが分かります。 IISでサービスをホストしていますが、IISはエンドポイントの動作設定を取得していませんでした(この記事http://stackoverflow.com/questions/8243914/wcf-service-hosted-in-iis-7で概説されている理由により) -binding-configuration-settings-are-ignored)になります。私はすべてがうまくいったことを固定したらすぐに。 –

+0

また、メッセージロギングが空のproto値を示していた理由は、ログを正しく読み取っていないためです。各要求のログには2つのエントリがあります。プロトタイプノードが空の "ServiceLevelSendRequest"のソースを持つものの、エンコードされたストリームを持つ "TransportSend"のソースを持つ2つ目のものがありました。 –

+0

最後に、_metaDataPreparedバグを発見してくれてありがとう(これはテスト中の簡単な方法でした。実際のメソッドはプライベートになり、基本クラスの静的コンストラクタで一度呼び出されます)。 EntityTypeは元のDataContractorとして格納されていましたが、デシリアライゼーションはコンストラクタをコールしません(呼び出すことができる実装可能な別のメソッドがありますが、単に格納する方が簡単でした)。 継承番号について...それ以上のアドバイスはありますか? (ナンバーがユニークであることを確認するために私の単体テストでテストしています。ありがとう。 –

0

私はthisごとにDataContractの代わりにProtoContractを使用する必要があると思っていましたか?また、サービス参照をコンフィグレーションするときは、「参照されたアセンブリで型を再利用する」を設定してください。そして、thisによれば、彼らはデータ契約をサポートしていますが、あなたは[DataMember(Order = 0)](少なくとも私のために働いたもの)の注文を設定する必要があります。

+1

TH OPはアセンブリ共有を使用していると述べているので、「再利用タイプ」は不要です。明示的な 'Order'sも設定されています。 –

+0

ありがとう私は訂正した。 _metaDataPrepared = trueはどこに設定しますか? –

+0

私は答えを含む完全な例にリンクしました –