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>