9

WCF client is receiving a Date value from a Java web service where the date sent to the client in XML is :

<sampleDate>2010-05-10+14:00</sampleDate>

Now the WCF client receiving this date is in timezone (+08:00) and when the client deserialises the Date value it is converted into the following DateTime value :

2010-05-09 18:00 +08:00

However we would like to ignore the +14:00 being sent from the server so that the serialised Date value in the client is :

2010-05-10

Note that the +14:00 is not consistent and may be +10:00, +11:00 etc so it is not possible to use DateTime conversions on the client side to get the desired date value.

How can this be easily achieved in WCF?

Thanks in advance.

UPDATE

Would the correct WCF solution to this be to implement IClientMessageFormatter?

SOLUTION

The cleanest solution for me was to implement the IClientMessageFormatter as suggested above. Here is an example :

C# code

public class ClientMessageFormatter : IClientMessageFormatter
{
    IClientMessageFormatter original;

    public ClientMessageFormatter(IClientMessageFormatter actual)
    {
        this.original = actual;
    }

    public void RemoveTimeZone(XmlNodeList nodeList)
    {
        if (nodeList != null)
        {
            foreach (XmlNode node in nodeList)
            {
                node.InnerText = Regex.Replace(node.InnerText, @"[\+\-]\d\d:\d\d", "");
            }
        }
    }

    #region IDispatchMessageFormatter Members

    public object DeserializeReply(Message message, object[] parameters)
    {
        Message newMessage = null;
        Message tempMessage;
        MessageBuffer buffer;
        MemoryStream ms;
        XmlDocument doc;
        XmlDictionaryReader reader;
        XmlReader xr;
        XmlWriter xw;

        try
        {
            buffer = message.CreateBufferedCopy(int.MaxValue);
            tempMessage = buffer.CreateMessage();
            reader = tempMessage.GetReaderAtBodyContents();

            if (reader != null)
            {
                doc = new XmlDocument();
                doc.Load(reader);
                reader.Close();

                if (doc.DocumentElement != null)
                {
                    /* enables switching between responses */
                    switch (doc.DocumentElement.LocalName)
                    {
                        case "sampleRootElement":
                            RemoveTimeZone(doc.DocumentElement.SelectNodes("childElement1/childElement2"));
                            break;
                    }
                }

                ms = new MemoryStream();
                xw = XmlWriter.Create(ms);
                doc.Save(xw);
                xw.Flush();
                xw.Close();

                ms.Position = 0;
                xr = XmlReader.Create(ms);

                newMessage = Message.CreateMessage(message.Version, null, xr);
                newMessage.Headers.CopyHeadersFrom(message);
                newMessage.Properties.CopyProperties(message.Properties);
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }

        return original.DeserializeReply((newMessage != null) ? newMessage : message, parameters);
    }

    public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
    {
        return original.SerializeRequest(messageVersion, parameters);
    }

    #endregion

}

public class ClientOperationBehavior : IOperationBehavior
{

    public void AddBindingParameters(OperationDescription description, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(OperationDescription description, ClientOperation proxy)
    {
        IClientMessageFormatter currentFormatter = proxy.Formatter;
        proxy.Formatter = new ClientMessageFormatter(currentFormatter);
    }

    public void ApplyDispatchBehavior(OperationDescription description, DispatchOperation operation)
    {
    }

    public void Validate(OperationDescription description)
    {
    }
}

public class ClientEndpointBehavior : IEndpointBehavior
{

    #region IEndpointBehavior Members
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        foreach (OperationDescription operation in endpoint.Contract.Operations)
        {
            operation.Behaviors.Add(new ClientOperationBehavior());
        }
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }
    #endregion
}

public class ClientBehaviorExtensionElement : BehaviorExtensionElement
{
    public override Type BehaviorType
    {
        get
        {
            return typeof(ClientEndpointBehavior);
        }
    }

    protected override object CreateBehavior()
    {
        return new ClientEndpointBehavior();
    }
}

App.config/Web.config

The client configuration file would then use the above formatter as follows :

<system.serviceModel>
    ....
    ....
    <extensions>
        <behaviorExtensions>
            <add name="clientMessageFormatterBehavior" type="..." />
        </behaviorExtensions>
    </extensions>
    <behaviors>
        <endpointBehaviors>
            <behavior name="clientMessageFormatterBehavior">
                <clientMessageFormatterBehavior />
            </behavior>
        </endpointBehaviors>
    </behaviors>
    <client>
        <endpoint address="https://www.example.com/service" behaviorConfiguration="clientMessageFormatterBehavior" .... />
    </client>
    ....
    ....
</system.serviceModel>

References

Link

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Net_Dev
  • 91
  • 1
  • 3

2 Answers2

5

Hi my improved and more universal solution

public static class TimeZoneMessageHelper
{
    public static Message RemoveTimeZone(this Message message)
    {
        try
        {
            using (XmlDictionaryReader messageBodyReader = message.GetReaderAtBodyContents())
            {
                XmlDocument xmlDocument = new XmlDocument();
                Message returnMessage = null;
                xmlDocument.Load(messageBodyReader);

                RemoveTimeZone(xmlDocument);

                StringBuilder stringBuilder = new StringBuilder();
                using(XmlWriter xmlWriter = XmlWriter.Create(stringBuilder))
                {
                    xmlDocument.Save(xmlWriter);
                }

                // do not dispose to early
                XmlReader resultMessageBodyReader = XmlReader.Create(new StringReader(stringBuilder.ToString()));

                returnMessage = Message.CreateMessage(message.Version, null, resultMessageBodyReader);
                returnMessage.Headers.CopyHeadersFrom(message);
                returnMessage.Properties.CopyProperties(message.Properties);

                return returnMessage;
            }
        }
    }

    private static void RemoveTimeZone(XmlNode xmlNode)
    {            
        if (xmlNode.ChildNodes.Count == 0)
        {
            RemoveTimeZoneCore(xmlNode);
        }
        else
        {
            foreach(XmlNode node in xmlNode.ChildNodes)
                RemoveTimeZone(node);
        }            
    }

    public static void RemoveTimeZoneCore(XmlNode xmlNode)
    {
        if (xmlNode != null)
        {
            if (Regex.IsMatch(xmlNode.InnerText, @"^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d\d)?(\+|-)\d\d:\d\d$", RegexOptions.Compiled))
            {
                xmlNode.InnerText = xmlNode.InnerText.Substring(0, xmlNode.InnerText.Length - 6);                    
            }                
        }            
    }
}

public class RemoveTimeZoneClientMessageFormatter : IClientMessageFormatter
{
    private readonly IClientMessageFormatter original;

    public RemoveTimeZoneClientMessageFormatter(IClientMessageFormatter original)
    {
        this.original = original;
    }

    #region Implementation of IClientMessageFormatter

    public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
    {
        return original.SerializeRequest(messageVersion, parameters).RemoveTimeZone();
    }

    public object DeserializeReply(Message message, object[] parameters)
    {
        return original.DeserializeReply(message.RemoveTimeZone() ?? message, parameters);
    }

    #endregion
}

public class RemoveTimeZoneDispatchMessageFormatter : IDispatchMessageFormatter
{
    private readonly IDispatchMessageFormatter original;

    public RemoveTimeZoneDispatchMessageFormatter(IDispatchMessageFormatter original)
    {
        this.original = original;
    }

    #region Implementation of IDispatchMessageFormatter

    public void DeserializeRequest(Message message, object[] parameters)
    {            
        original.DeserializeRequest(message.RemoveTimeZone() ?? message, parameters);
    }  

    public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
    {
        return original.SerializeReply(messageVersion, parameters, result).RemoveTimeZone();
    }

    #endregion
}

public class RemoveTimeZoneEndpointBehavior : IEndpointBehavior
{

    #region IEndpointBehavior Members
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {            
        foreach (OperationDescription operation in endpoint.Contract.Operations)
        {
            operation.Behaviors.Add(new RemoveTimeZoneOperationBehavior());
        }
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
        foreach(OperationDescription operation in endpoint.Contract.Operations)
        {
            operation.Behaviors.Add(new RemoveTimeZoneOperationBehavior());
        }
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }
    #endregion
}

public class RemoveTimeZoneOperationBehavior : IOperationBehavior
{

    public void AddBindingParameters(OperationDescription description, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(OperationDescription description, ClientOperation proxy)
    {            
        proxy.Formatter = new RemoveTimeZoneClientMessageFormatter(proxy.Formatter);
    }

    public void ApplyDispatchBehavior(OperationDescription description, DispatchOperation operation)
    {
        operation.Formatter = new RemoveTimeZoneDispatchMessageFormatter(operation.Formatter);
    }

    public void Validate(OperationDescription description)
    {
    }
}

public class RemoveTimeZoneExtensionElement : BehaviorExtensionElement
{
    public override Type BehaviorType
    {
        get
        {
            return typeof(RemoveTimeZoneEndpointBehavior);
        }
    }

    protected override object CreateBehavior()
    {
        return new RemoveTimeZoneEndpointBehavior();
    }
}\
Łukasz Rozmej
  • 249
  • 2
  • 8
  • Good solution (the best we can do if we don't use DateTimeOffset). However, your RegEx missing fractions: I had to modify it like this: `"^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d{1,10})?(\+|-)\d\d:\d\d$"` – HolisticElastic Oct 17 '13 at 12:59
  • Just FYI. I've been using this class for more than half year in production. There is one issue with formatters... They eat NewLine characters. I worked around this one by disabling DeserializeXXX methods (RemoveTimeZone only in SerizalizeXXX, if both client and server follow behavior then it should do the trick). Also I've added XmlWritterSettings with NewLineHandling=Entitize. – HolisticElastic May 13 '14 at 14:08
  • Why not using the likes of \d{4}-\d{2}-\d{2}? I find the other unnecessarily difficult to read. – steviesama Jun 28 '15 at 14:55
3

Try this

DateTime newDate = DateTime.SpecifyKind(oldDate, DateTimeKind.Unspecified);

Coding Best Practices Using DateTime in the .NET Framework

Related links

http://daveonsoftware.blogspot.com/2008/07/wcf-datetime-field-adjusted.html

http://social.msdn.microsoft.com/forums/en-US/wcf/thread/36ae825a-ffc6-4ac3-9981-c82692039d58

Best practices for DateTime serialization in .NET 3.5

Community
  • 1
  • 1
hgulyan
  • 8,099
  • 8
  • 50
  • 75
  • 2
    Thanks for the prompt reply but this does not work on the client since it is already deserialised. To my understanding, DateTimeKind only works when sending date/time value to another service. This issue is in receiving timezone from a client and having that timezone ignored. – Net_Dev Jun 18 '10 at 06:45
  • In that case, maybe you should ignore string after + ? It would be smth like oldDate.subString(oldDate.indexOf("+"), oldDate.length - oldDate.indexOf("+")) – hgulyan Jun 18 '10 at 06:56
  • thanks again for the prompt reply. that is the direction we are leaning towards, having all DateTime values as String to enable manually manipulation of date. question though...what happens if add Service Reference is used on project and code is auto generated? it would generate classes as DateTime instead of string. – Net_Dev Jun 18 '10 at 06:58
  • Thanks for your help. Posted my solution above using IClientMessageFormatter. – Net_Dev Jun 19 '10 at 02:08