How to pass a Noda Time object (or any third-party type) as a parameter to WCF?

I have a service that uses the Noda time types ( LocalDate and ZonedDateTime ) in the OperationContract parameters, but when I try to send, for example, LocalDate(1990,7,31) , the server receives an object with the default value (1970/1/1) . The error is not generated by the client or server.

Previously, it worked well with the corresponding BCL types ( DateTimeOffset ). I understand that Noda Time types cannot be “known” by WCF, but I don’t see how I should add them. I checked this page in the documentation for known types , but that doesn't help.

Is there a way to do this to avoid dirty (and possibly incomplete) manual conversion / serialization from and to BCL type?

Thanks.

+6
source share
1 answer

Thanks to Aron’s suggestion, I was able to come up with an IDataContractSurrogate implementation that is very useful for passing non-base type objects through WCF (not just Noda Time).

For those interested, here is the full explanation code that supports LocalDate, LocalDateTime, and ZonedDateTime. Of course, the serialization method can be configured to satisfy requirements, for example, using Json.NET serialization, since my simple implementation will not serialize the era / calendar information.

Alternatively, I posted the full code for this Gist: https://gist.github.com/mayerwin/6468178 .

Firstly, a helper class that serializes / converts to base types:

 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=(.*)$"); } 

Then the ReplacementType class, which contains serialized data (Serialized should only store types that are known to the WCF serializer) and can be passed through WCF:

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

Serialization / deserialization rules are wrapped in Translator common classes to simplify adding rules to a surrogate (only one surrogate is assigned to the service endpoint, so it must contain all the necessary rules):

 public abstract class Translator { public abstract object Serialize(object obj); public abstract object Deserialize(object obj); } public class Translator<TOriginal, TSerialized> : Translator { private readonly Func<TOriginal, TSerialized> _Serialize; private readonly Func<TSerialized, TOriginal> _Deserialize; public Translator(Func<TOriginal, TSerialized> serialize, Func<TSerialized, TOriginal> 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); } } 

Finally, a surrogate class, each translation rule can be easily added in a static constructor:

 public class CustomSurrogate : IDataContractSurrogate { /// Type.GetType only works for the current assembly or mscorlib.dll private static readonly Dictionary<string, Type> 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<Type, Translator> Translators; static CustomSurrogate() { Translators = new Dictionary<Type, Translator> { {typeof(LocalDate), new Translator<LocalDate, DateTime>(serialize: d => d.ToDateTime(), deserialize: d => d.ToLocalDate())}, {typeof(LocalDateTime), new Translator<LocalDateTime, DateTime>(serialize: d => d.ToDateTimeUnspecified(), deserialize: LocalDateTime.FromDateTime)}, {typeof(ZonedDateTime), new Translator<ZonedDateTime, string> (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<Type> 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(); } } 

And now, to use it, we define a service called SurrogateService :

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

To work on a completely autonomous basis with the client and server on the same computer (in the Console application), we just need to add the following code to the static class and call the Start () function:

 public static class SurrogateServiceTest { public static void DefineSurrogate(ServiceEndpoint endPoint, IDataContractSurrogate surrogate) { foreach (var operation in endPoint.Contract.Operations) { var ob = operation.Behaviors.Find<DataContractSerializerOperationBehavior>(); 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<ISurrogateService>(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(); } } 

Howl! :)

+3
source

Source: https://habr.com/ru/post/953222/


All Articles