Как я могу XML сериализировать свойство DateTimeOffset? - PullRequest
19 голосов
/ 31 июля 2010

Свойство DateTimeOffset, которое есть в этом классе, не отображается, когда данные представлены в формате Xml. Что мне нужно сделать, чтобы сериализация Xml отображала это как DateTime или 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; }
}

и некоторые примеры данных ...

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

26 голосов
/ 05 марта 2014

Это на несколько лет позже, но вот быстрый и простой способ полностью сериализации DateTimeOffset с использованием 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;
14 голосов
/ 15 ноября 2011

Я придумал эту структуру, которая реализует сериализацию XML на основе форматирования ISO 8601 (например, 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"));
    }
}
5 голосов
/ 07 января 2011

Я тоже не уверен в лучшем способе, но вот что я сделал:

[XmlElement("lastUpdatedTime")]
public string lastUpdatedTimeForXml
{
  get { return lastUpdatedTime.ToString(); }
  set { lastUpdatedTime = DateTimeOffset.Parse(value); }
}
[XmlIgnore] 
public DateTimeOffset lastUpdatedTime;
4 голосов
/ 30 сентября 2015

Я нашел решение здесь: http://tneustaedter.blogspot.com/2012/02/proper-way-to-serialize-and-deserialize.html

Замена XmlSerializer на DataContractSerializer работает потрясающе. Смотрите пример кода ниже:

    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);
        }
    }
2 голосов
/ 31 июля 2010

Я закончил тем, что просто делал это ...

Добавлены два метода расширения ...

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

2 голосов
/ 31 июля 2010

Один из способов решения этой проблемы состоит в том, чтобы ваш класс реализовал интерфейс IXmlSerializable.Реализация этого интерфейса вынуждает сериализатор вызывать «переопределенные» WriteXml и ReadXml методы.

что-то вроде этого:

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

это хлопотно, но я не могу по-другому.также это решение дает вам полный контроль над процессом сериализации (это положительный момент)

, если вам нравится это решение и вы хотите получить полное, пожалуйста, прокомментируйте, и я запишу его

относительно пространств имен- Я не думаю, что вы можете избавиться от этого (вероятно, я не получу бонусный балл).

0 голосов
/ 25 июня 2019

В дополнение к ответу Питера, если вы используете модель сущностей ADO.NET (.edmx) и, следовательно, все модификаторы доступа генерируются автоматически в частичных классах, вы можете отредактировать шаблон (MyDB.tt),он генерирует типы DateTimeOffset с модификатором internal.Просто замените метод 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)));
}
...