Xml.Serialization 具有属性值的对象 T
Xml.Serialization object T with attribute value
我在我的模型上使用 Xml 属性来处理我的模型序列化。
基础 class 是:
public class RequestRoot<T>
{
[XmlElement("Action")]
public T ActionNode { get; set; }
}
ActionNode
是 T 类型,可以是从字符串到复杂对象集合的任何内容。
示例:
<?xml version="1.0" encoding="UTF-8"?>
<RequestRoot>
<Action Type="TypeValue A">
<SomeData>Some data</SomeData>
</Action>
</RequestRoot>
<?xml version="1.0" encoding="UTF-8"?>
<RequestRoot>
<Action Type="TypeValue B">
<MyObjects>
<MyObject>
<ObjectValue1>Object Value 1-1</ObjectValue1>
<ObjectValue2>Object Value 2-1</ObjectValue2>
</MyObject>
<MyObject>
<ObjectValue1>Object Value 1-2</ObjectValue1>
<ObjectValue2>Object Value 2-2</ObjectValue2>
</MyObject>
</MyObjects>
</Action>
</RequestRoot>
我的问题是:是否可以在我的模型上使用 Xml 属性来编写 Type="TypeValue A"
或 Type="TypeValue B"
,具体取决于 T
是什么?
如果没有,我有什么选择?
开箱即用 XmlSerializer
无法做到这一点。那是因为您的 RequestRoot
class 是通用的,并且 XmlSerializer
根据 XML 元素名称和可能的 "xsi:type"
属性确定要创建的对象类型.但是,您的类型信息嵌入在根元素的子元素 Action
中,在必须分配根元素时无法访问。
您需要做的是手动读写 RequestRoot
包装器,然后使用 XmlSerializer
作为内容。例如:
public abstract class RequestRootBase
{
[XmlIgnore]
public abstract Type RequestType { get; }
[XmlIgnore]
public abstract Object RequestObject { get; }
}
public class RequestRoot<T> : RequestRootBase
{
public RequestRoot() { }
public RequestRoot(T ActionNode) { this.ActionNode = ActionNode; }
[XmlElement("Action")]
public T ActionNode { get; set; }
public override Type RequestType
{
get { return typeof(T); }
}
public override object RequestObject
{
get { return ActionNode; }
}
}
public static class RequestRootHelper
{
public static RequestRootBase CreateBase(object action)
{
if (action == null)
throw new ArgumentNullException();
var type = action.GetType();
return (RequestRootBase)Activator.CreateInstance(typeof(RequestRoot<>).MakeGenericType(type), new [] { action });
}
public static RequestRoot<T> Create<T>(T action)
{
return new RequestRoot<T> { ActionNode = action };
}
}
public abstract class RequestRootXmlSerializerBase
{
const string RequestRootElementName = "RequestRoot";
const string ActionElementName = "Action";
const string TypeAttributeName = "Type";
protected abstract Type BindToType(string name);
protected abstract string BindToName(Type type);
static string DefaultRootXmlElementNamespace(Type type)
{
var xmlType = type.GetCustomAttribute<XmlRootAttribute>();
if (xmlType != null && !string.IsNullOrEmpty(xmlType.Namespace))
return xmlType.Namespace;
return null;
}
public void Serialize(RequestRootBase root, XmlWriter writer)
{
writer.WriteStartDocument();
writer.WriteStartElement(RequestRootElementName);
writer.WriteStartElement(ActionElementName);
var typeName = BindToName(root.RequestType);
writer.WriteAttributeString(TypeAttributeName, typeName);
var serializer = new XmlSerializer(root.RequestType);
var rootNameSpace = DefaultRootXmlElementNamespace(root.RequestType);
var ns = new XmlSerializerNamespaces();
if (string.IsNullOrEmpty(rootNameSpace))
ns.Add("", "");
else
ns.Add("", rootNameSpace);
serializer.Serialize(writer, root.RequestObject, ns);
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndDocument();
}
public RequestRootBase Deserialize(XmlReader reader)
{
if (!reader.ReadToFollowing(RequestRootElementName))
return null;
if (!reader.ReadToFollowing(ActionElementName))
return null;
var typeName = reader[TypeAttributeName];
if (typeName == null)
return null;
var type = BindToType(typeName);
if (type == null)
throw new InvalidDataException(); // THROW AN EXCEPTION in this case
reader.ReadStartElement();
var serializer = new XmlSerializer(type);
var action = serializer.Deserialize(reader);
if (action == null)
return null;
return RequestRootHelper.CreateBase(action);
}
public string SerializeToString(RequestRootBase root)
{
using (var textWriter = new StringWriter())
{
var settings = new XmlWriterSettings() { Indent = true, IndentChars = " " }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
Serialize(root, xmlWriter);
return textWriter.ToString();
}
}
public RequestRootBase DeserializeFromString(string xml)
{
using (var sr = new StringReader(xml))
using (var xmlReader = XmlReader.Create(sr))
{
return Deserialize(xmlReader);
}
}
}
public class RequestRootXmlSerializer : RequestRootXmlSerializerBase
{
readonly Dictionary<string, Type> nameToType = new Dictionary<string, Type>();
readonly Dictionary<Type, string> typeToName = new Dictionary<Type, string>();
const string ListPrefix = "ArrayOf";
const string ListPostFix = "";
protected override string BindToName(Type type)
{
return typeToName[type];
}
protected override Type BindToType(string name)
{
return nameToType[name];
}
public RequestRootXmlSerializer(IEnumerable<Type> types)
{
if (types == null)
throw new ArgumentNullException();
foreach (var type in types)
{
if (type.IsInterface || type.IsAbstract)
throw new ArgumentException();
var name = DefaultXmlElementName(type);
nameToType.Add(name, type);
typeToName.Add(type, name);
}
}
static string DefaultXmlElementName(Type type)
{
if (type.IsGenericType
&& type.GetGenericTypeDefinition() == typeof(List<>))
{
var elementType = type.GetGenericArguments()[0];
return ListPrefix + DefaultXmlElementName(elementType) + ListPostFix;
}
else
{
var xmlRoot = type.GetCustomAttribute<XmlRootAttribute>();
if (xmlRoot != null && !string.IsNullOrEmpty(xmlRoot.ElementName))
return xmlRoot.ElementName;
var xmlType = type.GetCustomAttribute<XmlTypeAttribute>();
if (xmlType != null && !string.IsNullOrEmpty(xmlType.TypeName))
return xmlType.TypeName;
return type.Name;
}
}
}
您可能希望将我的类型到名称映射方案替换为您自己的方案;这只是一个原型。
然后像这样使用它:
[XmlRoot("A", Namespace="ATestNameSpace")]
public class ClassA
{
[XmlText]
public string Value { get; set; }
}
public class MyObject
{
public string ObjectValue1 { get; set; }
public string ObjectValue2 { get; set; }
}
public class TestClass
{
public static void Test()
{
var root1 = RequestRootHelper.Create(new ClassA { Value = "Some data" });
var root2 = RequestRootHelper.Create(new List<MyObject> { new MyObject { ObjectValue1 = "Object Value 1-1", ObjectValue2 = "Object Value 2-1" }, new MyObject { ObjectValue1 = "Object Value 1-2", ObjectValue2 = "Object Value 2-2" } });
var serializer = new RequestRootXmlSerializer(new[] { typeof(ClassA), typeof(List<ClassA>), typeof(MyObject), typeof(List<MyObject>) });
TestRootSerialization(root1, serializer);
TestRootSerialization(root2, serializer);
}
private static void TestRootSerialization<T>(RequestRoot<T> root, RequestRootXmlSerializer serializer)
{
var xml1 = serializer.SerializeToString(root);
Debug.WriteLine(xml1);
var root11 = serializer.DeserializeFromString(xml1);
Debug.Assert(root.GetType() == root11.GetType()); // NO ASSERT
var xml11 = serializer.SerializeToString(root11);
Debug.WriteLine(xml11);
Debug.Assert(xml1 == xml11); // NO ASSERT
}
}
这会为 ClassA
生成以下 XML 输出:
<RequestRoot>
<Action Type="A">
<A xmlns="ATestNameSpace">Some data</A>
</Action>
</RequestRoot>
对于List<MyObject>
:
<RequestRoot>
<Action Type="ArrayOfMyObject">
<ArrayOfMyObject>
<MyObject>
<ObjectValue1>Object Value 1-1</ObjectValue1>
<ObjectValue2>Object Value 2-1</ObjectValue2>
</MyObject>
<MyObject>
<ObjectValue1>Object Value 1-2</ObjectValue1>
<ObjectValue2>Object Value 2-2</ObjectValue2>
</MyObject>
</ArrayOfMyObject>
</Action>
</RequestRoot>
我在我的模型上使用 Xml 属性来处理我的模型序列化。
基础 class 是:
public class RequestRoot<T>
{
[XmlElement("Action")]
public T ActionNode { get; set; }
}
ActionNode
是 T 类型,可以是从字符串到复杂对象集合的任何内容。
示例:
<?xml version="1.0" encoding="UTF-8"?>
<RequestRoot>
<Action Type="TypeValue A">
<SomeData>Some data</SomeData>
</Action>
</RequestRoot>
<?xml version="1.0" encoding="UTF-8"?>
<RequestRoot>
<Action Type="TypeValue B">
<MyObjects>
<MyObject>
<ObjectValue1>Object Value 1-1</ObjectValue1>
<ObjectValue2>Object Value 2-1</ObjectValue2>
</MyObject>
<MyObject>
<ObjectValue1>Object Value 1-2</ObjectValue1>
<ObjectValue2>Object Value 2-2</ObjectValue2>
</MyObject>
</MyObjects>
</Action>
</RequestRoot>
我的问题是:是否可以在我的模型上使用 Xml 属性来编写 Type="TypeValue A"
或 Type="TypeValue B"
,具体取决于 T
是什么?
如果没有,我有什么选择?
开箱即用 XmlSerializer
无法做到这一点。那是因为您的 RequestRoot
class 是通用的,并且 XmlSerializer
根据 XML 元素名称和可能的 "xsi:type"
属性确定要创建的对象类型.但是,您的类型信息嵌入在根元素的子元素 Action
中,在必须分配根元素时无法访问。
您需要做的是手动读写 RequestRoot
包装器,然后使用 XmlSerializer
作为内容。例如:
public abstract class RequestRootBase
{
[XmlIgnore]
public abstract Type RequestType { get; }
[XmlIgnore]
public abstract Object RequestObject { get; }
}
public class RequestRoot<T> : RequestRootBase
{
public RequestRoot() { }
public RequestRoot(T ActionNode) { this.ActionNode = ActionNode; }
[XmlElement("Action")]
public T ActionNode { get; set; }
public override Type RequestType
{
get { return typeof(T); }
}
public override object RequestObject
{
get { return ActionNode; }
}
}
public static class RequestRootHelper
{
public static RequestRootBase CreateBase(object action)
{
if (action == null)
throw new ArgumentNullException();
var type = action.GetType();
return (RequestRootBase)Activator.CreateInstance(typeof(RequestRoot<>).MakeGenericType(type), new [] { action });
}
public static RequestRoot<T> Create<T>(T action)
{
return new RequestRoot<T> { ActionNode = action };
}
}
public abstract class RequestRootXmlSerializerBase
{
const string RequestRootElementName = "RequestRoot";
const string ActionElementName = "Action";
const string TypeAttributeName = "Type";
protected abstract Type BindToType(string name);
protected abstract string BindToName(Type type);
static string DefaultRootXmlElementNamespace(Type type)
{
var xmlType = type.GetCustomAttribute<XmlRootAttribute>();
if (xmlType != null && !string.IsNullOrEmpty(xmlType.Namespace))
return xmlType.Namespace;
return null;
}
public void Serialize(RequestRootBase root, XmlWriter writer)
{
writer.WriteStartDocument();
writer.WriteStartElement(RequestRootElementName);
writer.WriteStartElement(ActionElementName);
var typeName = BindToName(root.RequestType);
writer.WriteAttributeString(TypeAttributeName, typeName);
var serializer = new XmlSerializer(root.RequestType);
var rootNameSpace = DefaultRootXmlElementNamespace(root.RequestType);
var ns = new XmlSerializerNamespaces();
if (string.IsNullOrEmpty(rootNameSpace))
ns.Add("", "");
else
ns.Add("", rootNameSpace);
serializer.Serialize(writer, root.RequestObject, ns);
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndDocument();
}
public RequestRootBase Deserialize(XmlReader reader)
{
if (!reader.ReadToFollowing(RequestRootElementName))
return null;
if (!reader.ReadToFollowing(ActionElementName))
return null;
var typeName = reader[TypeAttributeName];
if (typeName == null)
return null;
var type = BindToType(typeName);
if (type == null)
throw new InvalidDataException(); // THROW AN EXCEPTION in this case
reader.ReadStartElement();
var serializer = new XmlSerializer(type);
var action = serializer.Deserialize(reader);
if (action == null)
return null;
return RequestRootHelper.CreateBase(action);
}
public string SerializeToString(RequestRootBase root)
{
using (var textWriter = new StringWriter())
{
var settings = new XmlWriterSettings() { Indent = true, IndentChars = " " }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
Serialize(root, xmlWriter);
return textWriter.ToString();
}
}
public RequestRootBase DeserializeFromString(string xml)
{
using (var sr = new StringReader(xml))
using (var xmlReader = XmlReader.Create(sr))
{
return Deserialize(xmlReader);
}
}
}
public class RequestRootXmlSerializer : RequestRootXmlSerializerBase
{
readonly Dictionary<string, Type> nameToType = new Dictionary<string, Type>();
readonly Dictionary<Type, string> typeToName = new Dictionary<Type, string>();
const string ListPrefix = "ArrayOf";
const string ListPostFix = "";
protected override string BindToName(Type type)
{
return typeToName[type];
}
protected override Type BindToType(string name)
{
return nameToType[name];
}
public RequestRootXmlSerializer(IEnumerable<Type> types)
{
if (types == null)
throw new ArgumentNullException();
foreach (var type in types)
{
if (type.IsInterface || type.IsAbstract)
throw new ArgumentException();
var name = DefaultXmlElementName(type);
nameToType.Add(name, type);
typeToName.Add(type, name);
}
}
static string DefaultXmlElementName(Type type)
{
if (type.IsGenericType
&& type.GetGenericTypeDefinition() == typeof(List<>))
{
var elementType = type.GetGenericArguments()[0];
return ListPrefix + DefaultXmlElementName(elementType) + ListPostFix;
}
else
{
var xmlRoot = type.GetCustomAttribute<XmlRootAttribute>();
if (xmlRoot != null && !string.IsNullOrEmpty(xmlRoot.ElementName))
return xmlRoot.ElementName;
var xmlType = type.GetCustomAttribute<XmlTypeAttribute>();
if (xmlType != null && !string.IsNullOrEmpty(xmlType.TypeName))
return xmlType.TypeName;
return type.Name;
}
}
}
您可能希望将我的类型到名称映射方案替换为您自己的方案;这只是一个原型。
然后像这样使用它:
[XmlRoot("A", Namespace="ATestNameSpace")]
public class ClassA
{
[XmlText]
public string Value { get; set; }
}
public class MyObject
{
public string ObjectValue1 { get; set; }
public string ObjectValue2 { get; set; }
}
public class TestClass
{
public static void Test()
{
var root1 = RequestRootHelper.Create(new ClassA { Value = "Some data" });
var root2 = RequestRootHelper.Create(new List<MyObject> { new MyObject { ObjectValue1 = "Object Value 1-1", ObjectValue2 = "Object Value 2-1" }, new MyObject { ObjectValue1 = "Object Value 1-2", ObjectValue2 = "Object Value 2-2" } });
var serializer = new RequestRootXmlSerializer(new[] { typeof(ClassA), typeof(List<ClassA>), typeof(MyObject), typeof(List<MyObject>) });
TestRootSerialization(root1, serializer);
TestRootSerialization(root2, serializer);
}
private static void TestRootSerialization<T>(RequestRoot<T> root, RequestRootXmlSerializer serializer)
{
var xml1 = serializer.SerializeToString(root);
Debug.WriteLine(xml1);
var root11 = serializer.DeserializeFromString(xml1);
Debug.Assert(root.GetType() == root11.GetType()); // NO ASSERT
var xml11 = serializer.SerializeToString(root11);
Debug.WriteLine(xml11);
Debug.Assert(xml1 == xml11); // NO ASSERT
}
}
这会为 ClassA
生成以下 XML 输出:
<RequestRoot>
<Action Type="A">
<A xmlns="ATestNameSpace">Some data</A>
</Action>
</RequestRoot>
对于List<MyObject>
:
<RequestRoot>
<Action Type="ArrayOfMyObject">
<ArrayOfMyObject>
<MyObject>
<ObjectValue1>Object Value 1-1</ObjectValue1>
<ObjectValue2>Object Value 2-1</ObjectValue2>
</MyObject>
<MyObject>
<ObjectValue1>Object Value 1-2</ObjectValue1>
<ObjectValue2>Object Value 2-2</ObjectValue2>
</MyObject>
</ArrayOfMyObject>
</Action>
</RequestRoot>