If you are serious about using SOAP, it won’t be long before you find out that if you need a full ISO date in the XML, it will not work using C# and SOAP, as DateTimeOffset is not supported from Microsoft.
Here is how you go about ensuring that you deliver an ISO DateTime formatted field in SOAP.
First, you’ll need to create a struct to hold the ISO 8601 Date Time Offset, and we will use IXmlSerializable interface which provides custom formatting for XML serialisation and deserialization.
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")); } }
We also need to cater for Json Date Time Offsets too, so we will write a converter
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; } }
Next, we need to implement it in an object model, with a slight twist as you can not use the Iso8601SerializableDateTimeOffset struct directly, to do this we need to wrap the result into a string property
public class Foo { public Guid Id { get; set; } [JsonConverter(typeof(UtcDateTimeOffsetConverter))] [XmlElement("AcquireDate")] public string acquireDateForXml { get { return AcquireDate.ToString(); } set { AcquireDate = DateTimeOffset.Parse(value); } } [XmlIgnore] public Iso8601SerializableDateTimeOffset? AcquireDate; }
That is it, job done
https://github.com/BryanAvery/DateTimeOffsetSOAP