2013-09-05 13 views
6

Ho un servizio che utilizza i tipi Noda Time (LocalDate e ZonedDateTime) nei parametri OperationContract, ma quando provo ad inviare per esempio LocalDate(1990,7,31) il server riceve un oggetto con il valore predefinito (1970/1/1). Nessun errore viene generato dal client o dal server.Come passare un oggetto Noda Time (o qualsiasi tipo di terze parti) come parametro in WCF?

precedenza funzionava bene con i corrispondenti tipi BCL (DateTimeOffset). Capisco che i tipi di Noda Time potrebbero non essere "conosciuti" da WCF, ma non vedo come dovrei aggiungerli. Ho controllato this page in the documentation about known types, ma non aiuta.

Esiste un modo per fare questo per evitare una sporca (e forse incompleta) di conversione/serializzazione da e verso un tipo di BCL?

Grazie.

+2

Suona come Jon non ha incluso gli attributi DataContract a Noda. Potrebbe essere necessario utilizzare il [interfaccia di IDataContractSurrogate] (http://msdn.microsoft.com/en-us/library/system.runtime.serialization.idatacontractsurrogate.aspx) – Aron

+1

Grazie Aron! Questo è stato molto utile. Sono stato in grado di creare un surrogato in no (da) tempo utilizzando http: //blogs.msdn.com/b/carlosfigueira/archive/2011/09/14/wcf-extensibility-serialization-surrogates.aspx e http://stackoverflow.com/questions/4742225/using-a-datacontractsurrogate-with-wcf-rest –

+0

NodaTime only attualmente supporta la serializzazione Json.NET e solo successivamente tramite 'NodaTime.Serialization.JsonNet' che non è attualmente compilato nella versione principale. Dovresti costruirlo da solo. Sono lieto di sapere che sei riuscito a ottenere questo risultato con i surrogati di DataContract. Sono interessato a vedere la tua implementazione. Sarebbe disposto a postarlo da qualche parte (GitHub, GIST, ecc.)? –

risposta

3

Grazie al suggerimento di Aron, sono stato in grado di creare un'implementazione di IDataContractSurrogate, che è molto utile per passare oggetti di tipi non di base tramite WCF (non solo Noda Time).

Per coloro che sono interessati, ecco il codice completo di spiegazioni, di supporto LocalDate, LocalDateTime e ZonedDateTime. Il metodo di serializzazione può naturalmente essere personalizzato per soddisfare i requisiti, ad esempio utilizzando la serializzazione Json.NET, in quanto la mia semplice implementazione non serializzerà le informazioni dell'era/calendario.

In alternativa, ho postato il codice completo su questo Gist: https://gist.github.com/mayerwin/6468178.

In primo luogo, la classe di supporto che si occupa di serializzazione/conversione di tipi di base:

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

seguita dalla classe ReplacementType che contiene i dati serializzati (serializzati deve solo tipi di archivi che sono noti dal serializzatore WCF) e può essere passato sopra WCF: regole

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

la serializzazione/deserializzazione sono avvolti in Translator classi generiche per semplificare l'aggiunta norme alla surrogato (unico surrogato è assegnato al servizio endpo int così dovrebbe contenere tutte le regole necessarie):

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); 
    } 
} 

Infine la classe surrogata, ogni regola traduzione può essere facilmente aggiunto nel costruttore statico:

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(); 
    } 
} 

E ora di usarlo, definiamo un servizio denominato 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); 
    } 
} 

Per eseguire in modo completamente autonomo con il client e il server sulla stessa macchina (in un'applicazione Console), abbiamo appena n eed per aggiungere il seguente codice a una classe statica e chiama la funzione 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<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(); 
    } 
} 

Voilà! :)

Problemi correlati