1

I deserialize an xml using a lot of serializable objects. I have read that in order to avoid not valid AllXsd value in DateTime, I have to create a helper string property which will handle the input as a string and convert it. i.e.

[XmlIgnore]
public DateTime? DateUpdated { get; set; }

[XmlElement("updateDate")]
public string DateUpdatedAsText
{
    set
    {
        if (!string.IsNullOrWhiteSpace(value))
        {
            try
            {
                DateUpdated = DateTime.Parse(value, CultureInfo.InvariantCulture);
            }
            catch (Exception) { }
        }
    }
    get
    {
        return DateUpdated.HasValue ? DateUpdated.Value.ToString("yyyy-MM-ddTHH:mm:ss") : null;
    }
}

This is tested and works great. However...

I have over 100 entities that contain DateTime fields in them, some of them more than one, and this solution is not very practical. I will have to implement this in all of them and if I want to change anything in the future I will have to do it again in all of them. How can I declare a general custom adapter to handle all DateTime types. In Java I can do this:

@XmlJavaTypeAdapter(CalendarXmlAdapter.class)
@Column(name = "updateDate")
private Calendar DateUpdated;

and in CalendarXmlAdapter.class specify the marshalling and unmarshalling

Is there a similar solution for C# System.Xml.Serialization.XmlSerializer?

Thanks in advance

EDITED

SOLUTION: It is a combination of @dbc comment and @steve16351 answer. I used the CustomDateTime class (with some minor changes in ReadXml, but non important) and in the declaration of the field SomeDate (which remained DateTime? type) I used it like this

    [XmlElement(ElementName = "updateDate", IsNullable = true, Type = typeof(CustomDateTime))]
    public DateTime? DateUpdated { get; set; }

The convertion takes place smoothly

AkiS
  • 47
  • 2
  • 9
  • 2
    `XmlSerializer` doesn't really support surrogate injection. The closest you can get is the neat trick from [Most elegant XML serialization of Color structure](https://stackoverflow.com/a/4322461/3744182) -- but unfortunately that doesn't work for primitives, and `DateTime` is a primitive. See https://dotnetfiddle.net/V56Yvt, which fails. Now oddly enough `DateTime?` (nullable datetime) is considered a primitive on some .Net versions, but not others! Here you can see the trick working on .Net core: https://dotnetfiddle.net/c6F7Ws and failing on .Net 4.5: https://dotnetfiddle.net/Nxfeov. – dbc Oct 08 '18 at 13:19
  • 1
    I combined the annotation of the Color structure to @steve16351 answer and there it goes! I edited my post to provide the combined answer. Thank you a lot! – AkiS Oct 08 '18 at 13:55

1 Answers1

1

You can use IXmlSerializable if you need more control over the deserialization. While you can't globally provide custom converters for specific types to the XmlSerializer to my knowledge, you could write a proxy DateTime class like below implementing IXmlSerializable, which in my view is more elegant than the solution you have of a string property and a corresponding DateTime property; and would disrupt the codebase less.

Here is an example of such a solution. It is also utilising implicit operators to avoid the need to convert to/from DateTime? in your own code.

class Program
{
    static void Main(string[] args)
    {
        XmlSerializer s = new XmlSerializer(typeof(MyClass));
        MyClass myClass = null;
        using (var sr = new StringReader(@"<myXml><updateDate>20181008</updateDate><someProp>Hello, world</someProp></myXml>"))            
            myClass = s.Deserialize(sr) as MyClass;            

        DateTime? myValue = myClass.SomeDate;
        Console.WriteLine($"{myClass.SomeDate}");
        Console.ReadKey();
    }
}

[XmlRoot("myXml")]
public class MyClass
{
    [XmlElement("updateDate")]
    public CustomDateTime SomeDate { get; set; }
    [XmlElement("someProp")]
    public string SomeProp { get; set; }

}

public class CustomDateTime : IXmlSerializable
{
    public DateTime? _dateTime { get; set; }
    private const string EXPECTED_FORMAT = "yyyyMMdd";

    public XmlSchema GetSchema()
    {
        throw new NotImplementedException();
    }

    public void ReadXml(XmlReader reader)
    {
        var elementContent = reader.ReadElementContentAsString();
        _dateTime = String.IsNullOrWhiteSpace(elementContent) ? (DateTime?)null : DateTime.ParseExact(elementContent, EXPECTED_FORMAT, CultureInfo.InvariantCulture);
    }

    public void WriteXml(XmlWriter writer)
    {
        if (!_dateTime.HasValue) return;
        writer.WriteString(_dateTime.Value.ToString(EXPECTED_FORMAT));
    }

    public static implicit operator DateTime? (CustomDateTime input)
    {
        return input._dateTime;
    }

    public static implicit operator CustomDateTime (DateTime input)
    {
        return new CustomDateTime() { _dateTime = input };
    }

    public override string ToString()
    {
        if (_dateTime == null) return null;
        return _dateTime.Value.ToString();
    }
}
steve16351
  • 5,372
  • 2
  • 16
  • 29
  • 1
    This is a very good suggestion. I made some changes to your code and edited my post to provide the complete workaround. Thank you very much – AkiS Oct 08 '18 at 13:53