我正在尝试使用protobuf来序列化我的WCF调用,但似乎该对象未被客户端序列化。有些事情需要注意:
我的主要方法如下
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没问题。关于可能导致问题的任何想法都非常感谢。
答案 0 :(得分:2)
K表;元素名称看起来正确(proto
,匹配XmlProtoSerializer.PROTO_ELEMENT
),所以protobuf-net绝对试图做某事。它也不包含@nil
来表示null
,因此它知道有数据。除此之外,它将对象序列化为MemoryStream
并将其写为base-64(与byte[]
等的表示方式相同,这允许WCF在{{1}之类的情况下以静默方式自动提升数据已启用)。所以问题变成了“为什么我的类型会序列化为什么?”
使用MTOM
/ DataContract
很好,并且与我现有的WCF集成测试相匹配。
我想知道这有多少是由于继承(显示的成员中只有一个与具体类型有关,我会猜测DataMember
是Blocked
,它具有特殊的处理能力)。
非常小的观察,但不需要存储0
- 它完全是冗余的,可以通过多态来处理而不需要存储。
此外,还有一个重要的错误,EntityType
永远不会设置为true。
然而!最终我无法重现这一点;我已经使用你的代码(或大部分代码)生成an integration test,并且 - 它通过了;意思是:使用WCF,_metaDataPrepared
,你的类(包括你的继承修复代码)和protobuf打包,只是工作。通过网络传输的数据是我们期望的数据。
很高兴尝试进一步提供帮助,但我需要能够重复它...而且现在,我不能。
我要做的第一件事就是添加缺少的NetTcpBinding
以查看是否有帮助。
答案 1 :(得分:0)
我认为你需要使用ProtoContract而不是每this SO的DataContract?此外,请确保在配置服务引用时设置“在引用的程序集中重用类型”。根据{{3}}所以他们支持数据合同,但你必须设置订单[DataMember(Order = 0)](至少这对我有用)。