我正在尝试通过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
这样的方法)来确定每个对象的加载时间是什么时候?
修改:
我应该指出,在反序列化之后暴露类似于我可以调用的OnLoaded()
方法的显而易见的情况,让我感到“不好做”。
EDIT2:
为了便于讨论,这是我当前的 hack “方法”,它适用于基本级别,但子City节点仍然认为需要保存更改(在现实世界中对象模型)是一个复杂得多,但这至少会编译,而不需要完整的源代码)
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;
}
}
答案 0 :(得分:14)
嗯...它仍然不是很漂亮,但你可以将你的反序列化逻辑重构为一个专用类,它可以在将反序列化对象返回给调用者之前通知反序列化对象它是否来自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;
}
}
}
答案 1 :(得分:4)
接受的解决方案对我来说并不适用。被覆盖的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));
}
}
}
}
}
答案 2 :(得分:2)
我尝试了abatishchev提供的解决方案,但正如他的回答下面的评论所指出的那样,自定义序列化器中的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
方法。
答案 3 :(得分:1)
一个棘手的问题,因为XmlSerializer
不支持序列化回调事件。有什么方法可以使用DataContractSerializer
吗?那个 ,但不允许属性(如上面的@name
)。
否则;你可以实现IXmlSerializable
,但那是很多的工作,而且非常容易出错。
否则 - 也许是通过堆栈检查调用者,但是非常脆弱,并且闻起来很成熟。
答案 4 :(得分:1)
在第一次回答后浪费了一些时间后,我从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。更新了对象树上递归漫步的帖子。
答案 5 :(得分:0)
我使用一个工厂方法,在反序列化XML结构化对象后添加更多逻辑。这种逻辑包括恢复对象成员之间的内部关系(child-parent,sibling ..)。
答案 6 :(得分:0)
在我的情况下,它是一个对象的集合,因此使用了一个例外的解决方案,必须稍微修改它
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);
}
}
}
}
然后一切都运转良好
答案 7 :(得分:0)
被接受的解决方案对我也不起作用。
为了使其最终起作用,我需要对HotN的解决方案进行一些修改。 我特别添加了
propertyInfo.GetIndexParameters().Length
检查(根据:https://docs.microsoft.com/en-us/dotnet/api/system.reflection.propertyinfo.getvalue),以避免参数不匹配异常。
我还有一些不应该被映射的属性。我将其归为[XmlIgnore],但提供的解决方案仍在处理它们。 但是添加一个检查传递的参数对象是否为null的技巧就可以了。
namespace Custom.Xml.Serialization
{
public interface IXmlDeserializationCallback
{
void OnXmlDeserialization(object sender);
}
public class CustomXmlSerializer : XmlSerializer
{
public CustomXmlSerializer(Type type) : base(type) { }
public new object Deserialize(Stream stream)
{
var result = base.Deserialize(stream);
CheckForDeserializationCallbacks(result);
return result;
}
private void CheckForDeserializationCallbacks(object deserializedObject)
{
if (deserializedObject == null)
return;
var deserializationCallback = deserializedObject as IXmlDeserializationCallback;
if (deserializationCallback != null)
{
deserializationCallback.OnXmlDeserialization(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
{
if (propertyInfo.GetIndexParameters().Length == 0)
CheckForDeserializationCallbacks(propertyInfo.GetValue(deserializedObject));
}
}
}
}
}