我如何XML序列化DateTimeOffset属性?

时间:2010-07-31 06:04:51

标签: .net xml-serialization

当数据表示为Xml时,此类中的DateTimeOffset属性不会被呈现。我需要做些什么才能告诉Xml序列化将其正确呈现为DateTimeDateTimeOffset

[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; }
}

和一些样本数据......

<?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>

更新

我希望有可能通过属性我可以在属性上装饰......

奖金问题

有什么方法可以摆脱根节点中声明的那两个命名空间?我应该吗?

7 个答案:

答案 0 :(得分:25)

这已经晚了几年,但是这是使用ISO 8601 完全序列化DateTimeOffset的快捷方式:

[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;

答案 1 :(得分:14)

我想出了这个结构,它实现了基于ISO 8601格式的XML序列化(例如2011-11-11T15:05:46.4733406+01:00)。提示:尝试解析诸如DateTime之类的2011-11-11T15:05:46值会按预期失败。

欢迎反馈。我没有在这里包含单元测试,因为那文本太多了。

/// <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"));
    }
}

答案 2 :(得分:5)

我也不确定最好的方法,但这就是我所做的:

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

答案 3 :(得分:4)

我在这里找到了解决方案:http://tneustaedter.blogspot.com/2012/02/proper-way-to-serialize-and-deserialize.html

用DataContractSerializer替换XmlSerializer非常棒。 请参阅以下示例代码:

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

答案 4 :(得分:2)

解决此问题的一种方法是让您的类实现接口IXmlSerializable。 实现此界面会强制序列化程序调用“覆盖的”WriteXmlReadXml方法。

类似的东西:

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...
}

当你读到它时:

DateTimeOffset offset;

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

这是一个麻烦,但我不能用任何其他方式。 此解决方案还可让您完全控制序列化过程(这是好处)

如果你喜欢这个解决方案,并想要完整的解决方案请注释,我会写下来

关于命名空间 - 我认为你不能摆脱它(我不会得到奖励分数)。

答案 5 :(得分:2)

我最终只是这样做......

添加了两种扩展方法......

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

修改了ViewData类......

[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; }
}

更改了我设置viewdata属性的方式...

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

完成。不确定这是否是最佳方式..但现在viewdata属性对于Json或Xml具有相同的值。

答案 6 :(得分:0)

要补充@Peter的答案,如果您使用的是ADO.NET实体模型(.edmx),因此所有访问修饰符都是在局部类中自动生成的,则可以编辑模板(MyDB.tt)来制作它使用internal修饰符生成DateTimeOffset类型。只需将Property()方法替换为下面的方法即可。

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