7

I need to replace the DateTime serialization for JSON in WCF REST Self Hosted service. Right now, I'm using something like the following code to do it, but it's definitely not the way to go since it requires manipulating each class.

[DataContract]
public class Test
{
    [IgnoreDataMember]
    public DateTime StartDate;

    [DataMember(Name = "StartDate")]
    public string StartDateStr
    {
        get { return DateUtil.DateToStr(StartDate); }
        set { StartDate = DateTime.Parse(value); }
    }
}

where my utility function DateUtil.DateToStr does all the formatting work.

Is there any easy way to do it without having to touch the attributes on my classes which have the DataContract attribute? Ideally, there would be no attributes, but a couple of lines of code in my configuration to replace the serializer with one where I've overridden DateTime serialization.

Everything that I've found looks like I have to replace huge pieces of the pipeline.

This article doesn't appear to apply because in I'm using WebServiceHost not HttpServiceHost, which not part of the 4.5.1 Framework.

JSON.NET Serializer for WCF REST Services

Community
  • 1
  • 1
bpeikes
  • 3,495
  • 9
  • 42
  • 80

3 Answers3

3

By default WCF uses DataContractJsonSerializer to serialize data into JSON. Unfortunatelly date from this serializer is in very difficult format to parse by human brain.

"DateTime": "\/Date(1535481994306+0200)\/"

To override this behavior we need to write custom IDispatchMessageFormatter. This class will receive all data which should be returned to requester and change it according to our needs.

To make it happen to the operations in the endpoint add custom formatter - ClientJsonDateFormatter:

ServiceHost host=new ServiceHost(typeof(CustomService));
host.AddServiceEndpoint(typeof(ICustomContract), new WebHttpBinding(), Consts.WebHttpAddress);

foreach (var endpoint in host.Description.Endpoints)
{
    if (endpoint.Address.Uri.Scheme.StartsWith("http"))
    {
        foreach (var operation in endpoint.Contract.Operations)
        {
            operation.OperationBehaviors.Add(new ClientJsonDateFormatter());
        }
        endpoint.Behaviors.Add(new WebHttpBehavior());
     }
 }

ClientJsonDateFormatter is simple class which just applies formatter ClientJsonDateFormatter

public class ClientJsonDateFormatter : IOperationBehavior
{
    public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) {  }

    public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) { }

    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
    {
        dispatchOperation.Formatter = new ResponseJsonFormatter(operationDescription);
    }

    public void Validate(OperationDescription operationDescription) { }
}

In the formatter we took imput and serialize it with the changed Serializer:

public class ResponseJsonFormatter : IDispatchMessageFormatter
{
    OperationDescription Operation;
    public ResponseJsonFormatter(OperationDescription operation)
    {
        this.Operation = operation;
    }

    public void DeserializeRequest(Message message, object[] parameters)
    {
    }

    public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
    {
        string json=Newtonsoft.Json.JsonConvert.SerializeObject(result);
        byte[] bytes = Encoding.UTF8.GetBytes(json);
        Message replyMessage = Message.CreateMessage(messageVersion, Operation.Messages[1].Action, new RawDataWriter(bytes));
        replyMessage.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Raw));
        return replyMessage;
    }
}

And to send information to client we need data writer - RawDataWriter. Its implementation is simple:

class RawDataWriter : BodyWriter
{
    byte[] data;
    public RawDataWriter(byte[] data)
        : base(true)
    {
        this.data = data;
    }

    protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
    {
        writer.WriteStartElement("Binary");
        writer.WriteBase64(data, 0, data.Length);
        writer.WriteEndElement();
    }
}

Applying all code will result in returning date in more friendly format:

"DateTime":"2018-08-28T20:56:48.6411976+02:00"

To show it in practice I created example in the github branch DateTimeFormatter.

Please check also this answer as very likely you also will need it.

Pawel Wujczyk
  • 422
  • 3
  • 12
2

There is a limitation in JSON to convert DateTime, specially according to your case.

Please see http://msdn.microsoft.com/en-us/library/bb412170(v=vs.110).aspx and read the section Dates/Times and JSON

To resolve this problem, I simply changed the type of serialization from JSON to XML for all the calls including DateTime.

DJ'
  • 1,760
  • 1
  • 13
  • 26
  • I've looked at the reference source for the WCF json serializer and it has a property called DateTimeFormat, but I can't figure out how to change it. I've posted on MSDN forum as well, but no one seems to know how to access it. The article you link to doesn't say it's not possible, nor does it say that there is a limitation. It just talks about the format that is used by default. – bpeikes Sep 29 '14 at 01:57
  • @bpeikes there is a limitation. I forgot the exact link where I saw the limitation details, but did you try XML? It will work fine with XML. Also see how the data is serialized in JSON, something like /DATE(7002340200+3000)/ – DJ' Sep 29 '14 at 15:04
  • @bpeikes Please go through the following two links: http://stackoverflow.com/questions/10302902/can-you-tell-json-net-to-serialize-datetime-as-utc-even-if-unspecified http://www.hanselman.com/blog/OnTheNightmareThatIsJSONDatesPlusJSONNETAndASPNETWebAPI.aspx – DJ' Sep 29 '14 at 15:23
  • We already have a huge amount of code which uses json on the client side. I've used my work around on the server. So we can continue using that. I've read both articles you posted. Neither of them talk about the fact that the DataContractJsonSerializer, which is part of WCF has a member called DateTimeFormat. Download the reference source for WCF and you'll see that it's there. – bpeikes Sep 29 '14 at 20:46
-1

After long time discussion ,I have find out the solution for it. Please Use the following Code to Solve serialized date..

[IgnoreDataMember]
public DateTime? PerformanceDate { get; set; }

[DataMember(EmitDefaultValue = false, Name = "PerformanceDate")]
public string UpdateStartDateStr
{
    get
    {
        if (this.PerformanceDate.HasValue)
            return this.PerformanceDate.Value.ToUniversalTime().ToString("s", CultureInfo.InvariantCulture);
        else
            return null;
    }
    set
    {
        // should implement this...
    }
}
Zac Faragher
  • 963
  • 13
  • 26
Ragupathy
  • 149
  • 3
  • Your solution is exactly what I proposed in the question. – bpeikes Mar 01 '16 at 20:26
  • 2
    My service has 96 DateTimes. Should I sit down and make this change 96 times? What if I miss one or I forget to apply the pattern in the future. A more centralized way would be better. – Micah Epps Jul 11 '20 at 16:20