如何将Noda Time(或任何第三方类型)对象作为参数传递给WCF?

我有一个在OperationContract参数中使用Noda Time类型( LocalDateZonedDateTime )的服务,但是当我尝试发送例如LocalDate(1990,7,31) ,服务器接收一个具有默认值(1970/1/1)的对象。 客户端或服务器不会引发任何错误。

以前它适用于相应的BCL类型( DateTimeOffset )。 我理解NCF时间类型可能不会被WCF“知道”,但我看不出我应该如何添加它们。 我在关于已知类型的文档中检查了此页面 ,但它没有帮助。

有没有办法做到这一点,以避免BCL类型的脏(可能不完整)手动转换/序列化?

谢谢。

感谢Aron的建议,我能够提出IDataContractSurrogate的实现,这非常有助于通过WCF传递非基类型的对象(不仅仅是Noda Time)。

对于那些感兴趣的人,这里有完整的代码和解释,支持LocalDate,LocalDateTime和ZonedDateTime。 序列化方法当然可以定制以满足要求,例如使用Json.NET序列化,因为我的简单实现不会序列化时代/日历信息。

或者,我已在此Gist上发布完整代码: https : //gist.github.com/mayerwin/6468178 。

首先,负责序列化/转换为基类型的辅助类:

 public static class DatesExtensions { public static DateTime ToDateTime(this LocalDate localDate) { return new DateTime(localDate.Year, localDate.Month, localDate.Day); } public static LocalDate ToLocalDate(this DateTime dateTime) { return new LocalDate(dateTime.Year, dateTime.Month, dateTime.Day); } public static string Serialize(this ZonedDateTime zonedDateTime) { return LocalDateTimePattern.ExtendedIsoPattern.Format(zonedDateTime.LocalDateTime) + "@O=" + OffsetPattern.GeneralInvariantPattern.Format(zonedDateTime.Offset) + "@Z=" + zonedDateTime.Zone.Id; } public static ZonedDateTime DeserializeZonedDateTime(string value) { var match = ZonedDateTimeRegex.Match(value); if (!match.Success) throw new InvalidOperationException("Could not parse " + value); var dtm = LocalDateTimePattern.ExtendedIsoPattern.Parse(match.Groups[1].Value).Value; var offset = OffsetPattern.GeneralInvariantPattern.Parse(match.Groups[2].Value).Value; var tz = DateTimeZoneProviders.Tzdb.GetZoneOrNull(match.Groups[3].Value); return new ZonedDateTime(dtm, tz, offset); } public static readonly Regex ZonedDateTimeRegex = new Regex(@"^(.*)@O=(.*)@Z=(.*)$"); } 

然后是一个包含序列化数据的ReplacementType类(Serialized应该只存储WCF序列化程序已知的类型)并且可以通过WCF传递:

 public class ReplacementType { [DataMember(Name = "Serialized")] public object Serialized { get; set; } [DataMember(Name = "OriginalType")] public string OriginalTypeFullName { get; set; } } 

序列化/反序列化规则包含在Translatorgenerics类中,以简化向代理项添加规则(只有一个代理项被分配给服务端点,因此它应包含所有必需的规则):

 public abstract class Translator { public abstract object Serialize(object obj); public abstract object Deserialize(object obj); } public class Translator : Translator { private readonly Func _Serialize; private readonly Func _Deserialize; public Translator(Func serialize, Func deserialize) { this._Serialize = serialize; this._Deserialize = deserialize; } public override object Serialize(object obj) { return new ReplacementType { Serialized = this._Serialize((TOriginal)obj), OriginalTypeFullName = typeof(TOriginal).FullName }; } public override object Deserialize(object obj) { return this._Deserialize((TSerialized)obj); } } 

最后是代理类,每个转换规则都可以在静态构造函数中轻松添加:

 public class CustomSurrogate : IDataContractSurrogate { /// Type.GetType only works for the current assembly or mscorlib.dll private static readonly Dictionary AllLoadedTypesByFullName = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).Distinct().GroupBy(t => t.FullName).ToDictionary(t => t.Key, t => t.First()); public static Type GetTypeExt(string typeFullName) { return Type.GetType(typeFullName) ?? AllLoadedTypesByFullName[typeFullName]; } private static readonly Dictionary Translators; static CustomSurrogate() { Translators = new Dictionary { {typeof(LocalDate), new Translator(serialize: d => d.ToDateTime(), deserialize: d => d.ToLocalDate())}, {typeof(LocalDateTime), new Translator(serialize: d => d.ToDateTimeUnspecified(), deserialize: LocalDateTime.FromDateTime)}, {typeof(ZonedDateTime), new Translator (serialize: d => d.Serialize(), deserialize: DatesExtensions.DeserializeZonedDateTime)} }; } public Type GetDataContractType(Type type) { if (Translators.ContainsKey(type)) { type = typeof(ReplacementType); } return type; } public object GetObjectToSerialize(object obj, Type targetType) { Translator translator; if (Translators.TryGetValue(obj.GetType(), out translator)) { return translator.Serialize(obj); } return obj; } public object GetDeserializedObject(object obj, Type targetType) { var replacementType = obj as ReplacementType; if (replacementType != null) { var originalType = GetTypeExt(replacementType.OriginalTypeFullName); return Translators[originalType].Deserialize(replacementType.Serialized); } return obj; } public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType) { throw new NotImplementedException(); } public object GetCustomDataToExport(Type clrType, Type dataContractType) { throw new NotImplementedException(); } public void GetKnownCustomDataTypes(Collection customDataTypes) { throw new NotImplementedException(); } public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData) { throw new NotImplementedException(); } public CodeTypeDeclaration ProcessImportedType(CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit) { throw new NotImplementedException(); } } 

现在要使用它,我们定义一个名为SurrogateService的服务:

 [ServiceContract] public interface ISurrogateService { [OperationContract] Tuple GetParams(LocalDate localDate, LocalDateTime localDateTime, ZonedDateTime zonedDateTime); } public class SurrogateService : ISurrogateService { public Tuple GetParams(LocalDate localDate, LocalDateTime localDateTime, ZonedDateTime zonedDateTime) { return Tuple.Create(localDate, localDateTime, zonedDateTime); } } 

要在完全独立的基础上运行客户端和服务器在同一台机器上(在控制台应用程序中),我们只需要将以下代码添加到静态类并调用函数Start ():

 public static class SurrogateServiceTest { public static void DefineSurrogate(ServiceEndpoint endPoint, IDataContractSurrogate surrogate) { foreach (var operation in endPoint.Contract.Operations) { var ob = operation.Behaviors.Find(); ob.DataContractSurrogate = surrogate; } } public static void Start() { var baseAddress = "http://" + Environment.MachineName + ":8000/Service"; var host = new ServiceHost(typeof(SurrogateService), new Uri(baseAddress)); var endpoint = host.AddServiceEndpoint(typeof(ISurrogateService), new BasicHttpBinding(), ""); host.Open(); var surrogate = new CustomSurrogate(); DefineSurrogate(endpoint, surrogate); Console.WriteLine("Host opened"); var factory = new ChannelFactory(new BasicHttpBinding(), new EndpointAddress(baseAddress)); DefineSurrogate(factory.Endpoint, surrogate); var client = factory.CreateChannel(); var now = SystemClock.Instance.Now.InUtc(); var p = client.GetParams(localDate: now.Date, localDateTime: now.LocalDateTime, zonedDateTime: now); if (p.Item1 == now.Date && p.Item2 == now.LocalDateTime && p.Item3 == now) { Console.WriteLine("Success"); } else { Console.WriteLine("Failure"); } ((IClientChannel)client).Close(); factory.Close(); Console.Write("Press ENTER to close the host"); Console.ReadLine(); host.Close(); } } 

沃利亚! 🙂