Как узнать, когда вы загрузились через XML-сериализацию?
Я пытаюсь загрузить дерево объектов через сериализацию XML, и на данный момент он будет загружать объекты и создавать дерево довольно счастливо. Моя проблема связана с тем, что эти классы поддерживают уровень аудита. То, что я хотел бы сделать, это вызвать некоторый метод для каждого объекта после его загрузки.
Для аргумента предположим, что у меня есть довольно общее дерево объектов с разными классами на разных уровнях, например:
<Customer name="Foo Bar Inc.">
<Office IsHq="True">
<Street>123 Any Street</Street>
<Town name="Anytown">
<State name="Anystate">
<Country name="My Country" />
</State>
</Town>
</Office>
<Office IsHq="False">
<Street>456 High Street</Street>
<Town name="Anycity">
<State name="Anystate">
<Country name="My Country" />
</State>
</Town>
</Office>
</Customer>
Можно ли использовать сериализаторы по умолчанию (аналогично тому, как вы можете создавать такие методы, как ShouldSerializeFoo
), чтобы определить, когда загрузка завершена для каждого объекта?
Edit:
Я должен указать, что очевидный случай разоблачения чего-то, похожего на метод OnLoaded()
, который я мог бы назвать после десериализации, нападает на меня как на "плохую вещь".
Edit2:
Для обсуждения это мой текущий hack "подход", который работает на базовом уровне, но ребенок City node все еще считает, что он должен быть сохранен с изменениями (в реальном мире объектная модель намного сложнее, но это, по крайней мере, будет скомпилировано без необходимости полного источника)
public class Office
{
[XmlAttribute("IsHq")]
public bool IsHeadquarters { get; set; }
[XmlElement]
public string Street { get; set; }
[XmlElement]
public Town Town { get; set; }
protected virtual void OnLoaded() {}
public static OfficeCollection Search()
{
OfficeCollection retval = new OfficeCollection();
string xmlString = @"
<Office IsHq='True'>
<Street>123 Any Street</Street>
<Town name='Anytown'>
<State name='Anystate'>
<Country name='My Country' />
</State>
</Town>
</Office>";
XmlSerializer xs = new XmlSerializer(retval.GetType());
XmlReader xr = new XmlTextReader(xmlString);
retval = (OfficeCollection)xs.Deserialize(xr);
foreach (Office thisOffice in retval)
{
thisOffice.OnLoaded();
}
return retval;
}
}
Ответы
Ответ 1
Хммм... это все еще не очень хорошо, но вы можете реорганизовать свою логику десериализации в выделенный класс, который мог бы уведомить десериализованный объект, что он возник из XML, прежде чем вернуть его вызывающему.
Обновление: Я думаю, что это должно быть довольно легко обойтись без слишком большого отклонения от шаблонов, установленных инфраструктурой... вам просто нужно убедиться, что вы используете CustomXmlSerializer. Классам, которые нуждаются в этом уведомлении, просто нужно реализовать IXmlDeserializationCallback
using System.Xml.Serialization;
namespace Custom.Xml.Serialization
{
public interface IXmlDeserializationCallback
{
void OnXmlDeserialization(object sender);
}
public class CustomXmlSerializer : XmlSerializer
{
protected override object Deserialize(XmlSerializationReader reader)
{
var result = base.Deserialize(reader);
var deserializedCallback = result as IXmlDeserializationCallback;
if (deserializedCallback != null)
{
deserializedCallback.OnXmlDeserialization(this);
}
return result;
}
}
}
Ответ 2
Принятое решение для меня не совсем сработало. Переопределенный метод Deserialize()
никогда не вызывался. Я считаю, что это потому, что этот метод не является общедоступным и поэтому вызывается одним (или более) общедоступными методами Deserialize()
, но не всеми из них.
Здесь реализация, которая работает путем скрытия метода и использует существующий интерфейс IDeserializationCallback
, поэтому любая десериализация с использованием методов, отличных от xml, может по-прежнему запускать метод OnDeserialization()
этого интерфейса. Он также использует отражение для перемещения дочерних свойств, чтобы увидеть, реализуют ли они также IDeserializationCallback
и соответственно их называет.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Serialization;
namespace Xml.Serialization
{
class XmlCallbackSerializer : XmlSerializer
{
public XmlCallbackSerializer(Type type) : base(type)
{
}
public XmlCallbackSerializer(XmlTypeMapping xmlTypeMapping) : base(xmlTypeMapping)
{
}
public XmlCallbackSerializer(Type type, string defaultNamespace) : base(type, defaultNamespace)
{
}
public XmlCallbackSerializer(Type type, Type[] extraTypes) : base(type, extraTypes)
{
}
public XmlCallbackSerializer(Type type, XmlAttributeOverrides overrides) : base(type, overrides)
{
}
public XmlCallbackSerializer(Type type, XmlRootAttribute root) : base(type, root)
{
}
public XmlCallbackSerializer(Type type, XmlAttributeOverrides overrides, Type[] extraTypes,
XmlRootAttribute root, string defaultNamespace) : base(type, overrides, extraTypes, root, defaultNamespace)
{
}
public XmlCallbackSerializer(Type type, XmlAttributeOverrides overrides, Type[] extraTypes,
XmlRootAttribute root, string defaultNamespace, string location)
: base(type, overrides, extraTypes, root, defaultNamespace, location)
{
}
public new object Deserialize(Stream stream)
{
var result = base.Deserialize(stream);
CheckForDeserializationCallbacks(result);
return result;
}
public new object Deserialize(TextReader textReader)
{
var result = base.Deserialize(textReader);
CheckForDeserializationCallbacks(result);
return result;
}
public new object Deserialize(XmlReader xmlReader)
{
var result = base.Deserialize(xmlReader);
CheckForDeserializationCallbacks(result);
return result;
}
public new object Deserialize(XmlSerializationReader reader)
{
var result = base.Deserialize(reader);
CheckForDeserializationCallbacks(result);
return result;
}
public new object Deserialize(XmlReader xmlReader, string encodingStyle)
{
var result = base.Deserialize(xmlReader, encodingStyle);
CheckForDeserializationCallbacks(result);
return result;
}
public new object Deserialize(XmlReader xmlReader, XmlDeserializationEvents events)
{
var result = base.Deserialize(xmlReader, events);
CheckForDeserializationCallbacks(result);
return result;
}
public new object Deserialize(XmlReader xmlReader, string encodingStyle, XmlDeserializationEvents events)
{
var result = base.Deserialize(xmlReader, encodingStyle, events);
CheckForDeserializationCallbacks(result);
return result;
}
private void CheckForDeserializationCallbacks(object deserializedObject)
{
var deserializationCallback = deserializedObject as IDeserializationCallback;
if (deserializationCallback != null)
{
deserializationCallback.OnDeserialization(this);
}
var properties = deserializedObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var propertyInfo in properties)
{
if (propertyInfo.PropertyType.GetInterface(typeof(IEnumerable<>).FullName) != null)
{
var collection = propertyInfo.GetValue(deserializedObject) as IEnumerable;
if (collection != null)
{
foreach (var item in collection)
{
CheckForDeserializationCallbacks(item);
}
}
}
else
{
CheckForDeserializationCallbacks(propertyInfo.GetValue(deserializedObject));
}
}
}
}
}
Ответ 3
Я попробовал решение, предоставленное абатищевым, но, как указано в комментариях ниже его ответа, метод Deserialize
в пользовательском сериализаторе никогда не будет вызван.
Мне удалось получить эту работу, перегрузив все различные перегрузки Deserialize
, которые мне нужны, чтобы он всегда вызывал пользовательский метод.
protected object Deserialize(System.IO.StringReader reader)
{
var result = base.Deserialize(reader);
CallBack(result);
return result;
}
protected object Deserialize(System.IO.TextReader reader)
{
var result = base.Deserialize(reader);
CallBack(result);
return result;
}
protected object Deserialize(System.Xml.XmlReader reader)
{
var result = base.Deserialize(reader);
CallBack(result);
return result;
}
protected object Deserialize(System.IO.Stream stream)
{
var result = base.Deserialize(stream);
CallBack(result);
return result;
}
private void CallBack(object result)
{
var deserializedCallback = result as IXmlDeserializationCallback;
if (deserializedCallback != null)
{
deserializedCallback.OnXmlDeserialization(this);
}
}
Таким образом, я действительно вижу способ вызова Deserialize
.
Ответ 4
Жесткий, поскольку XmlSerializer
не поддерживает события обратного вызова сериализации. Можно ли использовать DataContractSerializer
? Это делает, но не позволяет атрибуты (например, @name
выше).
В противном случае; вы можете реализовать IXmlSerializable
, но это очень много работы и очень подвержено ошибкам.
В противном случае - возможно, проверка вызывающего абонента через стек, но это очень хрупкий и запах зрелый.
Ответ 5
После некоторого времени с первым ответом я принял код из сообщения HotN, за исключением CheckForDeserializationCallbacks
:
private static void ProcessOnDeserialize(object _result) {
var type = _result != null ? _result.GetType() : null;
var methods = type != null ? type.GetMethods().Where(_ => _.GetCustomAttributes(true).Any(_m => _m is OnDeserializedAttribute)) : null;
if (methods != null) {
foreach (var mi in methods) {
mi.Invoke(_result, null);
}
}
var properties = type != null ? type.GetProperties().Where(_ => _.GetCustomAttributes(true).Any(_m => _m is XmlElementAttribute || _m is XmlAttributeAttribute)) : null;
if (properties != null) {
foreach (var prop in properties) {
var obj = prop.GetValue(_result, null);
var enumeration = obj as IEnumerable;
if (obj is IEnumerable) {
foreach (var item in enumeration) {
ProcessOnDeserialize(item);
}
} else {
ProcessOnDeserialize(obj);
}
}
}
}
Это позволяет использовать стандартный [OnDeserialized]
.
UPD. Обновлено сообщение для рекурсивного перехода по дереву объектов.
Ответ 6
Я использую метод factory, который добавляет больше логики после того, как структурированный объект XML был десериализован. Такая логика включает восстановление внутренних отношений (дочернего родителя, родного брата..) между членами объекта.
Ответ 7
В моем случае это была коллекция объектов, поэтому использовалось исключенное решение, чтобы немного изменить его
private static void PostDeserializedProcess<T>(T deserializedObj)
{
var deserializedCallback = deserializedObj as IXmlPostDeserializationCallback;
if (deserializedCallback != null)
{
deserializedCallback.OnXmlDeserialized(deserializedObj);
}
else
{
// it could be a List of objects
// and we need to check for every object in the list
var collection = deserializedObj as System.Collections.IEnumerable;
if (collection != null)
{
foreach (var item in collection)
{
PostDeserializedProcess(item);
}
}
}
}
И тогда все отлично работает
Ответ 8
Я тоже несколько старался заставить эти решения работать. Я нашел простейшее решение, чтобы мой огонь OnDeserialization()
вызывал обратный вызов, а XmlSerializer - это цепочка вызова BinaryFormatter впоследствии. У моего класса уже был метод GetClone()
, поэтому он был довольно простым и сбрасывал все мои попытки переопределить XmlSerializer
public static Foo Deserialize(string path) {
Foo foo;
XmlSerializer xmlSerializer = new XmlSerializer(typeof(Foo));
using (StreamReader textReader = new StreamReader(path)) {
foo = (Foo)xmlSerializer.Deserialize(textReader); // this does NOT fire the OnDeserialization callbacks
}
return foo.GetClone();
}
public Foo GetClone() {
using (var ms = new MemoryStream()) {
var formatter = new BinaryFormatter();
formatter.Serialize(ms, this);
ms.Position = 0;
return (Foo)formatter.Deserialize(ms); // this DOES fire the OnDeserialization callbacks
}
}