Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Web Api 2 DatetimeOffset XML Serialization Issue

Below is the property in my model:

 public DateTimeOffset AcquireDate { get; set; }

Below is the configuration in WebApiConfig:

config.Formatters.Clear();
config.Formatters.Add(new JsonMediaTypeFormatter());
config.Formatters.Add(new XmlMediaTypeFormatter()); 

Below is the json response (date is in IS 8601 format):

enter image description here

Below is the XML response:

<AcquireDate xmlns:d3p1="http://schemas.datacontract.org/2004/07/System">
      <d3p1:DateTime>2008-01-10T16:40:12.1523923Z</d3p1:DateTime>
      <d3p1:OffsetMinutes>330</d3p1:OffsetMinutes>
</AcquireDate>

From fiddler:

enter image description here

In Xml response, datetime and offset are available in two different elements. I want DateTimeOffset as single value, just like json response (in ISO 8601 format).

I could use one more property which would be of type string and that way my problem could be solved (empty setter requires for this property to be serialized).

[DataMember(Name="AcquireDate")]
    public string AcquireDateString
    {
        get
        {
            return AcquireDate.ToString("yyyy-MM-ddTHH:mm:ss.fffffffzzz");
        }
        set {
            AcquireDate = DateTimeOffset.Parse(value); 
        }
    }

Any other solution apart from that?

Solution should not affect existing Json Serialization which is working fine.

like image 491
Nilesh Thakkar Avatar asked Oct 18 '22 13:10

Nilesh Thakkar


1 Answers

Let's understand that DateTimeOffset is giving you the UTC time in addition to how much that value differs from UTC. Thus, the value always unambiguously identifies a single point in time. This is very valuable information you may not want to loose. But if because of requirements you must store just the offset, then continue reading the solution below.

Since you have flexibility to change your type from DateTimeOffset to a string, then maybe you can alter the type a bit and still use DateTimeOffset.

For example,

using System;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using Newtonsoft.Json;

namespace ConsoleApplication8
{
    public struct Iso8601SerializableDateTimeOffset : IXmlSerializable
    {
        public DateTimeOffset value;

        public Iso8601SerializableDateTimeOffset(DateTimeOffset value)
        {
            this.value = value;
        }

        public static implicit operator Iso8601SerializableDateTimeOffset(DateTimeOffset value)
        {
            return new Iso8601SerializableDateTimeOffset(value);
        }

        public static implicit operator DateTimeOffset(Iso8601SerializableDateTimeOffset instance)
        {
            return instance.value;
        }

        public static bool operator ==(Iso8601SerializableDateTimeOffset a, Iso8601SerializableDateTimeOffset b)
        {
            return a.value == b.value;
        }

        public static bool operator !=(Iso8601SerializableDateTimeOffset a, Iso8601SerializableDateTimeOffset b)
        {
            return a.value != b.value;
        }

        public static bool operator <(Iso8601SerializableDateTimeOffset a, Iso8601SerializableDateTimeOffset b)
        {
            return a.value < b.value;
        }

        public static bool operator >(Iso8601SerializableDateTimeOffset a, Iso8601SerializableDateTimeOffset b)
        {
            return a.value > b.value;
        }

        public override bool Equals(object o)
        {
            if (o is Iso8601SerializableDateTimeOffset)
                return value.Equals(((Iso8601SerializableDateTimeOffset)o).value);
            else if (o is DateTimeOffset)
                return value.Equals((DateTimeOffset)o);
            else
                return false;
        }

        public override int GetHashCode()
        {
            return value.GetHashCode();
        }

        public XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(XmlReader reader)
        {
            var text = reader.ReadElementString();
            value = DateTimeOffset.ParseExact(text, format: "o", formatProvider: null);
        }

        public override string ToString()
        {
            return value.ToString(format: "o");
        }

        public string ToString(string format)
        {
            return value.ToString(format);
        }

        public void WriteXml(XmlWriter writer)
        {
            writer.WriteString(value.ToString(format: "o"));
        }
    }

    public class Foo
    {
        public Guid Id { get; set; }

        [JsonConverter(typeof(UtcDateTimeOffsetConverter))]
        public Iso8601SerializableDateTimeOffset AcquireDate { get; set; }
    }


    class Program
    {
        static void Main(string[] args)
        {
            var foo = new Foo {
                Id = Guid.NewGuid(),
                AcquireDate = DateTimeOffset.Now
            };        

            var xmlSerializer = new System.Xml.Serialization.XmlSerializer(foo.GetType());
            xmlSerializer.Serialize(Console.Out, foo);
            Console.WriteLine();
            Console.ReadLine();
        }
    }
}

Output

<?xml version="1.0" encoding="IBM437"?>
<Foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Id>830cabe2-340b-42c6-bad4-12b5b8b1c43f</Id>
  <AcquireDate>2016-03-14T10:47:51.8162249-04:00</AcquireDate>
</Foo>

For JSON, we need a converter but we can reuse Newtonsoft.Json.Converters.IsoDateTimeConverter

  public class UtcDateTimeOffsetConverter : Newtonsoft.Json.Converters.IsoDateTimeConverter
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            if (value is Iso8601SerializableDateTimeOffset)
            {
                var date = (Iso8601SerializableDateTimeOffset)value;
                value = date.value;
            }
            base.WriteJson(writer, value, serializer);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            object value = base.ReadJson(reader, objectType, existingValue, serializer);
            if (value is Iso8601SerializableDateTimeOffset)
            {
                var date = (Iso8601SerializableDateTimeOffset)value;
                value = date.value;
            }
            return value;
        }
    }

The controller

public class ValuesController : ApiController
{
    public class Foo
    {
       public Guid Id { get; set; }

       [JsonConverter(typeof(UtcDateTimeOffsetConverter))]
       public Iso8601SerializableDateTimeOffset AcquireDate { get; set; }
    }

    // GET api/values
    public IEnumerable<Foo> Get()
    {
        return new Foo[] {
            new Foo() {
                Id = Guid.NewGuid(),
                AcquireDate = DateTimeOffset.Now
            }
        };
    }
 }

Output

<ArrayOfFoo xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/WebApplication1.Models">
    <Foo>
        <AcquireDate>2016-03-14T12:04:30.2791167-04:00</AcquireDate>
        <Id>b3188528-f854-454a-bf9f-9822ff27dc6f</Id>
    </Foo>
</ArrayOfFoo>

JSON

[{"Id":"e24bc769-3463-4320-b39a-9ff97e709142","AcquireDate":"2016-03-15T10:47:29.3061449-04:00"}]

Full example can be found on github: https://github.com/alexnolasco/DatetimeOffsetXMLSerializationExample

See also: How can I XML Serialize a DateTimeOffset Property?

Choosing Between DateTime, DateTimeOffset, TimeSpan, and TimeZoneInfo

like image 106
Alex Nolasco Avatar answered Nov 15 '22 06:11

Alex Nolasco