2012-12-23 36 views
9

Tôi đã có đoạn mã sau:Generic serialization Danh sách XML với các lớp khác nhau

BaseContent.cs

public class BaseContent 
{ 
    // Some auto properties 
} 

News.cs

public class News : BaseContent 
{ 
    // Some more auto properties 
} 

Events.cs

public class Event : BaseContent 
{ 
    // Some more auto properites 
} 

GenericResponse.cs

public class GenericResponse<T> 
{ 
    [XmlArray("Content")] 
    [XmlArrayItem("NewsObject", typeof(News)] 
    [XmlArrayItem("EventObject", typeof(Event)] 
    public List<T> ContentItems { get; set; } 
} 

NewsResponse.cs

public class NewsResponse : GenericResponse<News> {} 

EventResponse.cs

public class EventResponse : GenericResponse<Event> {} 

Như bạn có thể thấy, tôi có một lớp cơ sở BaseContent và hai lớp bắt nguồn từ nó. Tiếp theo tôi có một lớp đáp ứng chung, vì cấu trúc của các tệp xml luôn giống nhau, nhưng chúng khác nhau ở một số thuộc tính.

Tôi nghĩ tôi có thể chỉ định với [XmlArrayItem] tên nào để sử dụng cho một lớp cụ thể. Nhưng bây giờ tôi gặp lỗi:

System.InvalidOperationException: Unable to generate a temporary class (result=1). error CS0012: The type 'System.Object' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.

Tôi không thể thêm tham chiếu này vì tôi đang làm việc trên ứng dụng Windows 8.

Nếu tôi nhận xét một trong số [XmlArrayItem] hoạt động tốt.

Bất kỳ ai có ý tưởng để giải quyết vấn đề này?

Cập nhật tôi có thể không sử dụng DataContractSerializer, bởi vì tôi phải XmlAttributes sử dụng

Trả lời

11

Edit: Hãy để tải về demo project

Bạn đã không cung cấp tất cả các thuộc tính cho các đối tượng, do đó cho phép tôi để thêm một số - như một ví dụ:

public class BaseContent 
{ 
    [XmlAttribute("Name")] 
    public string Name { get; set; } 
} 

[XmlType(TypeName = "EventObject")] 
public class Event : BaseContent 
{ 
    [XmlAttribute("EventId")] 
    public int EventId { get; set; } 
} 

[XmlType(TypeName = "NewsObject")] 
public class News : BaseContent 
{ 
    [XmlAttribute("NewsId")] 
    public int NewsId { get; set; } 
} 

GenericResponse.cs có thể được xác định theo cách này - không cần phải chỉ định loại cho các mục mảng:

public class GenericResponse<T> 
{ 
    [XmlArray("Content")] 
    public List<T> ContentItems { get; set; } 

    public GenericResponse() 
    { 
     this.ContentItems = new List<T>(); 
    } 
} 

Và sau đó bạn có các lớp học đáp ứng:

public class EventResponse : GenericResponse<Event> 
{ 
} 

public class NewsResponse : GenericResponse<News> 
{ 
} 

Ví dụ 1: Serialize một đối tượng EventResponse

var response = new EventResponse 
{ 
    ContentItems = new List<Event> 
    { 
     new Event { 
      EventId = 1, 
      Name = "Event 1" 
     }, 

     new Event { 
      EventId = 2, 
      Name = "Event 2" 
     } 
    } 
}; 

string xml = XmlSerializer<EventResponse>.Serialize(response); 

Output XML:

<?xml version="1.0" encoding="utf-8"?> 
<EventResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
    <Content> 
     <EventObject Name="Event 1" EventId="1" /> 
     <EventObject Name="Event 2" EventId="2" /> 
    </Content> 
</EventResponse> 

Nếu bạn thử cùng với NewsResponse, nó sẽ hoạt động ne. BTW Tôi đang sử dụng số generic XmlSerializer của mình, nhấp vào liên kết để biết thêm về nó.

XmlSerializer.cs:

/// <summary> 
/// XML serializer helper class. Serializes and deserializes objects from/to XML 
/// </summary> 
/// <typeparam name="T">The type of the object to serialize/deserialize. 
/// Must have a parameterless constructor and implement <see cref="Serializable"/></typeparam> 
public class XmlSerializer<T> where T: class, new() 
{ 
    /// <summary> 
    /// Deserializes a XML string into an object 
    /// Default encoding: <c>UTF8</c> 
    /// </summary> 
    /// <param name="xml">The XML string to deserialize</param> 
    /// <returns>An object of type <c>T</c></returns> 
    public static T Deserialize(string xml) 
    { 
     return Deserialize(xml, Encoding.UTF8, null); 
    } 

    /// <summary> 
    /// Deserializes a XML string into an object 
    /// Default encoding: <c>UTF8</c> 
    /// </summary> 
    /// <param name="xml">The XML string to deserialize</param> 
    /// <param name="encoding">The encoding</param> 
    /// <returns>An object of type <c>T</c></returns> 
    public static T Deserialize(string xml, Encoding encoding) 
    { 
     return Deserialize(xml, encoding, null); 
    } 

    /// <summary> 
    /// Deserializes a XML string into an object 
    /// </summary> 
    /// <param name="xml">The XML string to deserialize</param> 
    /// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlReaderSettings"/></param> 
    /// <returns>An object of type <c>T</c></returns> 
    public static T Deserialize(string xml, XmlReaderSettings settings) 
    { 
     return Deserialize(xml, Encoding.UTF8, settings); 
    } 

    /// <summary> 
    /// Deserializes a XML string into an object 
    /// </summary> 
    /// <param name="xml">The XML string to deserialize</param> 
    /// <param name="encoding">The encoding</param> 
    /// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlReaderSettings"/></param> 
    /// <returns>An object of type <c>T</c></returns> 
    public static T Deserialize(string xml, Encoding encoding, XmlReaderSettings settings) 
    { 
     if (string.IsNullOrEmpty(xml)) 
      throw new ArgumentException("XML cannot be null or empty", "xml"); 

     XmlSerializer xmlSerializer = new XmlSerializer(typeof(T)); 

     using (MemoryStream memoryStream = new MemoryStream(encoding.GetBytes(xml))) 
     { 
      using (XmlReader xmlReader = XmlReader.Create(memoryStream, settings)) 
      { 
       return (T) xmlSerializer.Deserialize(xmlReader); 
      } 
     } 
    } 

    /// <summary> 
    /// Deserializes a XML file. 
    /// </summary> 
    /// <param name="filename">The filename of the XML file to deserialize</param> 
    /// <returns>An object of type <c>T</c></returns> 
    public static T DeserializeFromFile(string filename) 
    { 
     return DeserializeFromFile(filename, new XmlReaderSettings()); 
    } 

    /// <summary> 
    /// Deserializes a XML file. 
    /// </summary> 
    /// <param name="filename">The filename of the XML file to deserialize</param> 
    /// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlReaderSettings"/></param> 
    /// <returns>An object of type <c>T</c></returns> 
    public static T DeserializeFromFile(string filename, XmlReaderSettings settings) 
    { 
     if (string.IsNullOrEmpty(filename)) 
      throw new ArgumentException("filename", "XML filename cannot be null or empty"); 

     if (! File.Exists(filename)) 
      throw new FileNotFoundException("Cannot find XML file to deserialize", filename); 

     // Create the stream writer with the specified encoding 
     using (XmlReader reader = XmlReader.Create(filename, settings)) 
     { 
      System.Xml.Serialization.XmlSerializer xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(T)); 
      return (T) xmlSerializer.Deserialize(reader); 
     } 
    } 

    /// <summary> 
    /// Serialize an object 
    /// </summary> 
    /// <param name="source">The object to serialize</param> 
    /// <returns>A XML string that represents the object to be serialized</returns> 
    public static string Serialize(T source) 
    { 
     // indented XML by default 
     return Serialize(source, null, GetIndentedSettings()); 
    } 

    /// <summary> 
    /// Serialize an object 
    /// </summary> 
    /// <param name="source">The object to serialize</param> 
    /// <param name="namespaces">Namespaces to include in serialization</param> 
    /// <returns>A XML string that represents the object to be serialized</returns> 
    public static string Serialize(T source, XmlSerializerNamespaces namespaces) 
    { 
     // indented XML by default 
     return Serialize(source, namespaces, GetIndentedSettings()); 
    } 

    /// <summary> 
    /// Serialize an object 
    /// </summary> 
    /// <param name="source">The object to serialize</param> 
    /// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlWriterSettings"/></param> 
    /// <returns>A XML string that represents the object to be serialized</returns> 
    public static string Serialize(T source, XmlWriterSettings settings) 
    { 
     return Serialize(source, null, settings); 
    } 

    /// <summary> 
    /// Serialize an object 
    /// </summary> 
    /// <param name="source">The object to serialize</param> 
    /// <param name="namespaces">Namespaces to include in serialization</param> 
    /// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlWriterSettings"/></param> 
    /// <returns>A XML string that represents the object to be serialized</returns> 
    public static string Serialize(T source, XmlSerializerNamespaces namespaces, XmlWriterSettings settings) 
    { 
     if (source == null) 
      throw new ArgumentNullException("source", "Object to serialize cannot be null"); 

     string xml = null; 
     XmlSerializer serializer = new XmlSerializer(source.GetType()); 

     using (MemoryStream memoryStream = new MemoryStream()) 
     { 
      using (XmlWriter xmlWriter = XmlWriter.Create(memoryStream, settings)) 
      { 
       System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(typeof(T)); 
       x.Serialize(xmlWriter, source, namespaces); 

       memoryStream.Position = 0; // rewind the stream before reading back. 
       using (StreamReader sr = new StreamReader(memoryStream)) 
       { 
        xml = sr.ReadToEnd(); 
       } 
      } 
     } 

     return xml; 
    } 

    /// <summary> 
    /// Serialize an object to a XML file 
    /// </summary> 
    /// <param name="source">The object to serialize</param> 
    /// <param name="filename">The file to generate</param> 
    public static void SerializeToFile(T source, string filename) 
    { 
     // indented XML by default 
     SerializeToFile(source, filename, null, GetIndentedSettings()); 
    } 

    /// <summary> 
    /// Serialize an object to a XML file 
    /// </summary> 
    /// <param name="source">The object to serialize</param> 
    /// <param name="filename">The file to generate</param> 
    /// <param name="namespaces">Namespaces to include in serialization</param> 
    public static void SerializeToFile(T source, string filename, XmlSerializerNamespaces namespaces) 
    { 
     // indented XML by default 
     SerializeToFile(source, filename, namespaces, GetIndentedSettings()); 
    } 

    /// <summary> 
    /// Serialize an object to a XML file 
    /// </summary> 
    /// <param name="source">The object to serialize</param> 
    /// <param name="filename">The file to generate</param> 
    /// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlWriterSettings"/></param> 
    public static void SerializeToFile(T source, string filename, XmlWriterSettings settings) 
    { 
     SerializeToFile(source, filename, null, settings); 
    } 

    /// <summary> 
    /// Serialize an object to a XML file 
    /// </summary> 
    /// <param name="source">The object to serialize</param> 
    /// <param name="filename">The file to generate</param> 
    /// <param name="namespaces">Namespaces to include in serialization</param> 
    /// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlWriterSettings"/></param> 
    public static void SerializeToFile(T source, string filename, XmlSerializerNamespaces namespaces, XmlWriterSettings settings) 
    { 
     if (source == null) 
      throw new ArgumentNullException("source", "Object to serialize cannot be null"); 

     XmlSerializer serializer = new XmlSerializer(source.GetType()); 

     using (XmlWriter xmlWriter = XmlWriter.Create(filename, settings)) 
     { 
      System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(typeof(T)); 
      x.Serialize(xmlWriter, source, namespaces); 
     } 
    } 

    #region Private methods 


    private static XmlWriterSettings GetIndentedSettings() 
    { 
     XmlWriterSettings xmlWriterSettings = new XmlWriterSettings(); 
     xmlWriterSettings.Indent = true; 
     xmlWriterSettings.IndentChars = "\t"; 

     return xmlWriterSettings; 
    } 

    #endregion 
} 
+0

Câu trả lời thực sự hay, nhưng hiện tại không chính xác những gì tôi cần. Trong trường hợp của bạn, XML sẽ là và vân vân. Điều này sẽ không làm việc cho tôi. Trong trường hợp các sự kiện tôi cần EventObject, trong trường hợp tin tức tôi cần NewsObject. Tôi đã cố gắng làm điều này bằng cách thêm [XmlType ("EventObject")] vào lớp sự kiện của bạn, nhưng cũng không giúp được gì. Vâng, điều dễ nhất đối với tôi là đổi tên lớp "event" thành "EventObject" và loại bỏ [XmlArrayItem] -Attribute. Nhưng tôi không thích việc đặt tên đối tượng là "* Object". –

+0

@Raubi: Đó chỉ là một ví dụ, bạn có thể tùy chỉnh XML theo cách bạn muốn. Tôi không thể cung cấp cho bạn một câu trả lời tốt hơn bởi vì tôi không biết đầu ra XML nào bạn muốn. Vui lòng chỉnh sửa câu trả lời của bạn và bao gồm đầu ra XML dự kiến ​​cho các lớp của bạn. –

+0

@Raubi: Đã chỉnh sửa câu trả lời của tôi. Tôi không chắc liệu XML có chính xác những gì bạn cần hay không, nhưng tôi nghĩ rằng bạn sẽ tìm thấy tất cả những gì bạn cần để tùy chỉnh XML của bạn trong câu trả lời của tôi. Vui lòng tải xuống dự án demo, liên kết nằm ở trên cùng. PS: Chúc mừng năm mới! –

0

Xem xét sử dụng DataContractSerializer thay vì XmlSerializer.

XmlSerializer được tạo khi chạy một cụm tạm thời, để tăng tốc độ serialization - quá trình deserialization sau này. Vì vậy, nó đòi hỏi phải biên dịch mã.

DataContractSerializer mặt khác thì không. Trong trường hợp bạn sử dụng phương pháp DataContractSerializer , bạn phải chơi xung quanh với các thuộc tính thích hợp, để bạn kiểm soát các vấn đề "kế thừa" .

+0

Rất tiếc, DataContractSerializer không thể thực hiện được. Tôi cần có XmlAttributes –

3

Tôi đã tìm thấy giải pháp của mình. Không tốt nhất, nhưng nó đang hoạt động.

tôi impleted IXmlSerializable và handeled những thứ của bản thân mình:

public void ReadXml(System.Xml.XmlReader reader) 
{ 
    reader.Read(); 
    reader.MoveToContent(); 

    if (reader.LocalName == "AnotherNode") 
    { 
     var innerXml = Serializer<AnotherClass>.CreateSerializer(); 
     Remove = (AnotherClass) innerXml.Deserialize(reader); 
     reader.MoveToContent(); 
    } 

    reader.Read(); 

    // Here is the trick 
    if (reader.IsStartElement()) 
    { 
     do 
     { 
      var innerXml = Serializer<T>.CreateSerializer(); 

      var obj = (T) innerXml.Deserialize(reader); 
      Updates.Add(obj); 
     } while (reader.MoveToContent() == XmlNodeType.Element); 
    } 
} 

public void WriteXml(System.Xml.XmlWriter writer) 
{ 
    var removeSerializer = Serializer<RemoveElement>.CreateSerializer(); 
    removeSerializer.Serialize(writer, Remove); 

    if (Updates.Any()) 
    { 
     var innerXml = Serializer<T>.CreateSerializer(); 
     writer.WriteStartElement("ContentUpdates"); 
     foreach (var update in Updates) 
     { 
      innerXml.Serialize(writer, update); 
     } 
     writer.WriteEndElement(); 
    } 
} 
0

Đó âm thanh như một lỗi trong mã-emitter cho XmlSerializer cho nền tảng Windows Store cụ thể, nhưng nó là một chút tranh luận, bởi vì nó vẫn không thành công trên thường xuyên. NET, nhưng có một thông báo khác:

Unable to generate a temporary class (result=1).

error CS0030: Cannot convert type 'News' to 'Event'

error CS1502: The best overloaded method match for 'System.Collections.Generic.List.Add(News)' has some invalid arguments

error CS1503: Argument 1: cannot convert from 'Event' to 'News'

Về cơ bản, có vẻ như trường hợp không được hỗ trợ thể hiện khác nhau trong hai trường hợp. Về cơ bản, vấn đề là bạn đang chú thích (đối với T = News) "nếu điều này NewsNews, hãy gọi phần tử 'NewsObject'; nếu điều này NewsEvent, hãy gọi thành phần 'EventObject'" - lẻ vì một Newsbao giờ một Event vv

Tuy nhiên, những tin tức tốt lành là những gì bạn muốn làm có thể được thực hiện chỉ sử dụng [XmlType(...)]:

[XmlType("NewsObject")] 
public class News : BaseContent 
{ 
    // Some more auto properties 
    public int B { get; set; } 
} 
[XmlType("EventObject")] 
public class Event : BaseContent 
{ 
    // Some more auto properites 
    public int C { get; set; } 
} 
... 
public class GenericResponse<T> 
{ 
    [XmlArray("Content")] 
    public List<T> ContentItems { get; set; } 
} 

lưu ý: không [XmlArrayItem(...)] trong ab ove.

mà sau đó kết quả đầu ra (sau khi định dạng, làm sạch, vv):

<NewsResponse> 
    <Content> 
     <NewsObject><A>1</A><B>2</B></NewsObject> 
    </Content> 
</NewsResponse> 

qua mã kiểm tra:

var obj = new NewsResponse { ContentItems = new List<News> { 
    new News { A = 1, B = 2 } } }; 

var sw = new StringWriter(); 
using (var xw = System.Xml.XmlWriter.Create(sw)) 
{ 
    var ser = new XmlSerializer(obj.GetType()); 
    ser.Serialize(xw, obj); 
} 
string xml = sw.ToString(); 

Nếu bạn cần kiểm soát nhiều hơn thế, sau đó XmlAttributeOverrides là điều cần xem xét tại; nhưng bạn phải bộ nối tiếp bộ nhớ cache được tạo với XmlAttributeOverrides, để tránh các hội đồng xuất huyết và bị rò rỉ bộ nhớ.

+0

Cảm ơn câu trả lời của bạn. Điều đó cũng làm như vậy. :) –

Các vấn đề liên quan