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
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();
}
}