2015-04-03 117 views
1

我在我的模型上使用Xml属性来处理序列化我的模型。具有属性值的Xml.Serialization对象T

基类是:

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

开箱即用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 
    } 
} 

这将产生以下XML输出ClassA

<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> 
+0

DBC - 感谢您抽出宝贵的时间提供这样一个深入的答案!我希望避免这样的事情,但正如你所说,在这种情况下,仿制药的性质对我来说是不利的...... – KevinKode 2015-04-03 18:14:44