Created
September 6, 2013 18:45
-
-
Save mayerwin/6468178 to your computer and use it in GitHub Desktop.
Original question on stackoverflow here: http://stackoverflow.com/q/18632361/541420 ("How to pass a Noda Time (or any third-party type) object as a parameter in WCF?").
Credits to http://blogs.msdn.com/b/carlosfigueira/archive/2011/09/14/wcf-extensibility-serialization-surrogates.aspx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.CodeDom; | |
using System.Collections.Generic; | |
using System.Collections.ObjectModel; | |
using System.Linq; | |
using System.Reflection; | |
using System.Runtime.Serialization; | |
using System.ServiceModel; | |
using System.ServiceModel.Description; | |
using System.Text.RegularExpressions; | |
using NodaTime; | |
using NodaTime.Text; | |
namespace ServicesHost { | |
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=(.*)$"); | |
} | |
public static class SurrogateServiceTest { | |
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); | |
} | |
} | |
public class ReplacementType { | |
[DataMember(Name = "Serialized")] | |
public object Serialized { get; set; } | |
[DataMember(Name = "OriginalType")] | |
public string OriginalTypeFullName { get; set; } | |
} | |
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(); | |
} | |
} | |
[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); | |
} | |
} | |
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(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment