Pure.Krome
Pure.Krome

Reputation: 86947

How can I XML Serialize a DateTimeOffset Property?

The DateTimeOffset property I have in this class doesn't get rendered when the data is represented as Xml. What do I need to do to tell the Xml serialization to render that proper as a DateTime or DateTimeOffset?

[XmlRoot("playersConnected")]
public class PlayersConnectedViewData
{
    [XmlElement("playerConnected")]
    public PlayersConnectedItem[] playersConnected { get; set; }
}

[XmlRoot("playersConnected")]
public class PlayersConnectedItem
{
    public string name { get; set; }
    public DateTimeOffset connectedOn { get; set; }  // <-- This property fails.
    public string server { get; set; }
    public string gameType { get; set; }
}

and some sample data...

<?xml version="1.0" encoding="utf-8"?>
<playersConnected 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <playerConnected>
    <name>jollyroger1000</name>
    <connectedOn />
    <server>log1</server>
    <gameType>Battlefield 2</gameType>
  </playerConnected>
</playersConnected>

Update

I'm hoping there might be a way through an Attribute which I can decorate on the property...

Bonus Question

Any way to get rid of those two namespaces declared in the root node? Should I?

Upvotes: 31

Views: 20473

Answers (7)

Extragorey
Extragorey

Reputation: 1764

To supplement @Peter's answer, if you're using an ADO.NET Entities model (.edmx), and hence all the access modifiers are generated automatically in partial classes, you can edit the T4 template (expand the .edmx file in the solution explorer and open YourEdmxFilename.tt) to make it generate DateTimeOffset types with the internal modifier instead of the default.

Simply replace the Property(EdmProperty) method with the one below:

public string Property(EdmProperty edmProperty)
{
    string typeName = _typeMapper.GetTypeName(edmProperty.TypeUsage);
    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1} {2} {{ {3}get; {4}set; }}",
        typeName == "System.DateTimeOffset" || typeName == "Nullable<System.DateTimeOffset>" ? "internal" : Accessibility.ForProperty(edmProperty),
        typeName,
        _code.Escape(edmProperty),
        _code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
        _code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
}

Upvotes: -1

Peter
Peter

Reputation: 5728

It's a few years late, but here's the quick and easy way to completely serialize DateTimeOffset using ISO 8601:

[XmlElement("lastUpdatedTime")]
public string lastUpdatedTimeForXml // format: 2011-11-11T15:05:46.4733406+01:00
{
   get { return lastUpdatedTime.ToString("o"); } // o = yyyy-MM-ddTHH:mm:ss.fffffffzzz
   set { lastUpdatedTime = DateTimeOffset.Parse(value); } 
}
[XmlIgnore] 
public DateTimeOffset lastUpdatedTime;

Upvotes: 42

Itai Zolberg
Itai Zolberg

Reputation: 118

one way to solve this problem is have your class implement the interface IXmlSerializable. Implementing this interface forces the serializer to call the 'overridden' WriteXml and ReadXml mathods.

something like that :

public void WriteXml(XmlWriter w)
{
    wr.WriteStartElement("playersConnected"); 
    w.WriteElementString("name", Name);
    w.WriteElementString("connected-on" , ConnectedOn.ToString("dd.MM.yyyy HH:mm:ss"));
    //etc...
}

and when you read it :

DateTimeOffset offset;

if(DateTimeoffset.TryParse(reader.Value, out offset))
{
    connectedOn = offset;
}

it is a hassle but I cannot thing of any other way. also this solution gives you full control on your serialization process(this is the upside)

if you like this solution and want the complete one please comment and i will write it down

regarding the namespaces - i don't think you can get rid of it(i won't get the bonus score probably).

Upvotes: 2

Marek Malczewski
Marek Malczewski

Reputation: 694

I have found the solution here: http://tneustaedter.blogspot.com/2012/02/proper-way-to-serialize-and-deserialize.html

Replacing XmlSerializer with DataContractSerializer works awesome. See sample code below:

    public static string XmlSerialize(this object input)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            DataContractSerializer serializer = new DataContractSerializer(input.GetType());
            serializer.WriteObject(stream, input);
            return new UTF8Encoding().GetString(stream.ToArray());
        }
    }

    public static T XmlDeserialize<T>(this string input)
    {
        using (MemoryStream memoryStream = new MemoryStream(new UTF8Encoding().GetBytes(input)))
        {
            DataContractSerializer serializer = new DataContractSerializer(typeof(T));
            return (T)serializer.ReadObject(memoryStream);
        }
    }

Upvotes: 4

AndiDog
AndiDog

Reputation: 70128

I came up with this struct which implements XML serialization based on ISO 8601 formatting (e.g. 2011-11-11T15:05:46.4733406+01:00). Hint: Trying to parse a DateTime value such as 2011-11-11T15:05:46 fails as expected.

Feedback welcome. I didn't include the unit tests here because that would be too much text.

/// <remarks>
/// The default value is <c>DateTimeOffset.MinValue</c>. This is a value
/// type and has the same hash code as <c>DateTimeOffset</c>! Implicit
/// assignment from <c>DateTime</c> is neither implemented nor desirable!
/// </remarks>
public struct Iso8601SerializableDateTimeOffset : IXmlSerializable
{
    private 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"));
    }
}

Upvotes: 17

Mike
Mike

Reputation: 51

I'm also unsure of the best way, but here is what I did:

[XmlElement("lastUpdatedTime")]
public string lastUpdatedTimeForXml
{
  get { return lastUpdatedTime.ToString(); }
  set { lastUpdatedTime = DateTimeOffset.Parse(value); }
}
[XmlIgnore] 
public DateTimeOffset lastUpdatedTime;

Upvotes: 5

Pure.Krome
Pure.Krome

Reputation: 86947

I've ended up just doing this...

Added the two extension methods ...

public static double ToUnixEpoch(this DateTimeOffset value)
{
    // Create Timespan by subtracting the value provided from 
    //the Unix Epoch then return the total seconds (which is a UNIX timestamp)
    return (double)((value - new DateTime(1970, 1, 1, 0, 0, 0, 0)
        .ToLocalTime())).TotalSeconds;
}

public static string ToJsonString(this DateTimeOffset value)
{
    return string.Format("\\/Date({0})\\/", value.ToUnixEpoch());
}

Modified the ViewData class...

[XmlRoot("playersConnected")]
public class PlayersConnectedItem
{
    public string name { get; set; }
    public string connectedOn { get; set; }
    public string server { get; set; }
    public string gameType { get; set; }
}

Changed how I set the viewdata properties...

var data = (from q in connectedPlayerLogEntries
            select new PlayersConnectedItem
                       {
                           name = q.ClientName,
                           connectedOn =  q.CreatedOn.ToJsonString(),
                           server = q.GameFile.UniqueName,
                           gameType = q.GameFile.GameType.Description()
                        });

Done. Not sure if that's the best way .. but now that viewdata property has the same values for either Json or Xml.

Upvotes: 2

Related Questions