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

Posted

技术标签:

【中文标题】如何在 WCF 中将 Noda Time(或任何第三方类型)对象作为参数传递?【英文标题】:How to pass a Noda Time (or any third-party type) object as a parameter in WCF? 【发布时间】:2013-09-09 01:17:29 【问题描述】:

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

以前它与相应的 BCL 类型 (DateTimeOffset) 配合得很好。我知道 WCF 可能不“知道” Noda Time 类型,但我不知道应该如何添加它们。我检查了this page in the documentation about known types,但没有帮助。

有没有什么方法可以避免从 BCL 类型到 BCL 类型的脏(并且可能不完整)手动转换/序列化?

谢谢。

【问题讨论】:

听起来 Jon 在 Noda 中没有包含 DataContract 属性。您可能需要使用IDataContractSurrogate interface 谢谢阿伦!这很有帮助。我能够使用 blogs.msdn.com/b/carlosfigueira/archive/2011/09/14/… 和 ***.com/questions/4742225/… 在 no(da) 时间内创建一个代理 NodaTime 目前仅支持 Json.NET 序列化,并且仅通过 NodaTime.Serialization.JsonNet 当前未编译到主版本中。你必须自己建造它。我很高兴听到您能够使用 DataContract 代理进行此操作。我很想看看你的实现。是否愿意将其发布在某个地方(GitHub、GIST 等)? 嗨,马特,我已经在此处(作为答案)和要点上发布了带有解释的完整代码(更容易一次复制粘贴)。 【参考方案1】:

感谢 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; 

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

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

现在要使用它,我们定义一个名为 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);
    

要在同一台机器上完全独立运行客户端和服务器(在控制台应用程序中),我们只需将以下代码添加到静态类并调用函数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();
    

瞧! :)

【讨论】:

以上是关于如何在 WCF 中将 Noda Time(或任何第三方类型)对象作为参数传递?的主要内容,如果未能解决你的问题,请参考以下文章

Noda Time 在现有 MVC5 应用程序中的实现策略

如何在使用 WCF 的事务性 MSMQ 中将消息显式标记为中毒

如何在每个请求中将 winform 自定义用户凭据传递给 WCF 服务?

如何在 Visual Studio 2019 中将 WCF 服务添加到 .NET Core 项目?

如果 Json 变量在 WCF 中包含空格或任何特殊字符,如何获取 Json 值

如何在 WCF 中将接口标记为 DataContract