具有属性值的Xml.Serialization对象T.

时间:2015-04-03 06:44:36

标签: c# xml xml-serialization

我在我的模型上使用Xml属性来处理序列化我的模型。

基类是:

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是什么?

如果没有,我有哪些替代方案?

1 个答案:

答案 0 :(得分:1)

开箱即用XmlSerializer无法执行此操作。这是因为您的RequestRoot类是通用的,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>