Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change the json DateTime serialization in WCF 4.0 REST Service

Tags:

json

rest

wcf

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

like image 220
bpeikes Avatar asked Sep 17 '14 15:09

bpeikes


2 Answers

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.

like image 53
Pawel Wujczyk Avatar answered Nov 12 '22 14:11

Pawel Wujczyk


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.

like image 41
DJ' Avatar answered Nov 12 '22 13:11

DJ'