Как XML-сериализовать свойство DateTimeOffset?
Свойство 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>
Update
Я надеюсь, что может быть путь через Атрибут, который я могу украсить на свойстве...
Бонусный вопрос
Любой способ избавиться от этих двух пространств имен, объявленных в корневом каталоге node? Должен ли я?
Ответы
Ответ 1
Я закончил так просто...
Добавлены два метода расширения...
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
Это на несколько лет позже, но вот быстрый и простой способ полностью сериализовать 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;
Ответ 3
Я придумал эту структуру, которая реализует сериализацию 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"));
}
}
Ответ 4
Я также не уверен в лучшем виде, но вот что я сделал:
[XmlElement("lastUpdatedTime")]
public string lastUpdatedTimeForXml
{
get { return lastUpdatedTime.ToString(); }
set { lastUpdatedTime = DateTimeOffset.Parse(value); }
}
[XmlIgnore]
public DateTimeOffset lastUpdatedTime;
Ответ 5
Я нашел решение здесь: 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);
}
}
Ответ 6
Один из способов решения этой проблемы - реализовать ваш класс интерфейса IXmlSerializable
.
Реализация этого интерфейса заставляет сериализатор вызывать "переопределенные" WriteXml
и ReadXml
mathods.
что-то вроде этого:
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;
}
Это хлопот, но я не могу ничего другого.
также это решение дает вам полный контроль над процессом сериализации (это вверх)
если вам нравится это решение, и вы хотите, чтобы он был полным, прокомментируйте, и я напишу его
относительно пространств имен - я не думаю, что вы можете избавиться от него (я не получу бонусный балл).
Ответ 7
В дополнение к ответу @Peter, если вы используете модель ADO.NET Entities (.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)));
}