2016-06-30 6 views
3

Initial вопрос

Я хочу serializeList<IXmlSerializable> изменения XmlType из IXmlSerializable класса динамически (так что я не могу использовать атрибуты тегов, чтобы сделать это)C# XML сериализации переопределение Тип IXmlSerializable класса

Я попытался использовать XmlAttributeOverrides, чтобы сделать это без успеха до сих пор.

Ниже приведен пример кода, иллюстрирующий проблему:
IXmlSerializable класс (от MSDN):

public class Person : IXmlSerializable 
{ 
    // Private state 
    private string personName; 


    // Constructors 
    public Person(string name) 
    { 
     personName = name; 
    } 

    public Person() 
    { 
     personName = null; 
    } 


    // Xml Serialization Infrastructure 
    public void WriteXml(XmlWriter writer) 
    { 
     writer.WriteString(personName); 
    } 

    public void ReadXml(XmlReader reader) 
    { 
     personName = reader.ReadString(); 
    } 

    public XmlSchema GetSchema() 
    { 
     return (null); 
    } 


    // Print 
    public override string ToString() 
    { 
     return (personName); 
    } 
} 

Тест класс (с помощью консоли для вывода):

class Program 
{ 
    static void Main(string[] args) 
    { 
     List<Person> lPersonList = new List<Person> { 
      new Person("First"), 
      new Person("Second"), 
      new Person("Third") 
     }; 
     XmlAttributeOverrides lOverrides = new XmlAttributeOverrides(); 
     XmlAttributes lAttributes = new XmlAttributes { XmlType = new XmlTypeAttribute("Employee") }; 
     lOverrides.Add(typeof(Person), lAttributes); 

     XmlSerializer lSerialiser = new XmlSerializer(typeof(List<Person>), lOverrides, null, new XmlRootAttribute("Employees"), null); 
     XmlSerializerNamespaces lNamespaces = new XmlSerializerNamespaces(); 
     lNamespaces.Add("", ""); 
     lSerialiser.Serialize(Console.Out, lPersonList, lNamespaces); 

     System.Console.WriteLine("Enter any key to close."); 
     System.Console.ReadKey(); 
    } 
} 

Здесь это то, что я хочу получить:

<Employees> 
<Employee>First</Employee> 
<Employee>Second</Employee> 
<Employee>Third</Employee> 
</Employees> 

Но я получаю эту ошибку во время выполнения:

System.InvalidOperationException: Только атрибут XmlRoot может быть указан для типа лица. Для указания типа схемы используйте XmlSchemaProviderAttribute.

Примечание Когда мой Person класс не выполняет IXmlSerializable, все работает хорошо ...

Может кто-нибудь помочь мне в этом?


решение Выбирается (основано на @dbc answer)

Как @dbc отметил, используя класс «суррогатный» это самый простой способ сделать то, что я хочу. Но, как я уже сказал, мне нужно динамически изменять тип Person, а это значит, что я не могу использовать теги атрибутов.
Так что я до сих пор использую XmlAttributeOverrides в моем окончательном проекте, здесь:

суррогатные List<Person> Класс (так же, как @dbc без атрибутов тегов):

public class EmployeesListSurrogate 
{ 
    public List<Person> EmployeeList { get; set; } 

    public static implicit operator List<Person>(EmployeesListSurrogate surrogate) 
    { 
     return surrogate == null ? null : surrogate.EmployeeList; 
    } 

    public static implicit operator EmployeesListSurrogate(List<Person> employees) 
    { 
     return new EmployeesListSurrogate { EmployeeList = employees }; 
    } 
} 

Тест класс с использованием суррогатным:

class Program 
{ 
    static void Main(string[] args) 
    { 
     List<Person> lPersonList = new List<Person> { 
      new Person("First"), 
      new Person("Second"), 
      new Person("Third") 
     }; 

     XmlAttributeOverrides lOverrides = new XmlAttributeOverrides(); 
     XmlAttributes lEmployeesListAttributes = new XmlAttributes { XmlRoot = new XmlRootAttribute("Employees") }; 
     lOverrides.Add(typeof(EmployeesListSurrogate), lEmployeesListAttributes); 
     XmlAttributes lEmployeeAttributes = new XmlAttributes { XmlElements = { new XmlElementAttribute("Employee") } }; 
     lOverrides.Add(typeof(EmployeesListSurrogate), "EmployeeList", lEmployeeAttributes); 

     XmlSerializer lSerializer = new XmlSerializer(typeof(EmployeesListSurrogate), lOverrides); 
     XmlSerializerNamespaces lNamespaces = new XmlSerializerNamespaces(); 
     lNamespaces.Add("", ""); 
     lSerializer.Serialize(Console.Out, (EmployeesListSurrogate)lPersonList, lNamespaces); 
    } 
} 

Я хочу завершить это с большой благодарностью @dbc, ваш ответ был очень полезным и информацией Я многому научился. Я не могу продвигать вас, но я надеюсь, что сообщество сделает это!

ответ

2

Самый простой способ получить XML вы хотите бы сериализовать класс «суррогатный» следующим образом:

[XmlRoot("Employees")] 
public class EmployeesListSurrogate 
{ 
    [XmlElement("Employee")] 
    public List<Person> EmployeeList { get; set; } 

    public static implicit operator List<Person>(EmployeesListSurrogate surrogate) 
    { 
     return surrogate == null ? null : surrogate.EmployeeList; 
    } 

    public static implicit operator EmployeesListSurrogate(List<Person> employees) 
    { 
     return new EmployeesListSurrogate { EmployeeList = employees }; 
    } 
} 

Это полностью исключает необходимость XmlAttributeOverrides. Или вы можете использовать XmlAttributeOverrides вместе с XmlAttributes.XmlElements, чтобы указать имя XML для EmployeeList динамически.

Это, как говорится, причина, по которой InvalidOperationException выбрасывается при попытке применить [XmlType] к типу, который также реализует IXmlSerializable что XmlSerializer требует имя типа должны быть возвращены через совершенно иной механизм, а именно в XmlSchemaProviderAttribute.MethodName метод, указанный в [XmlSchemaProvider] атрибут.

Когда [XmlSchemaProvider] применяется к IXmlSerializable типа, XmlSerializer будет искать общественный статический метод типа, имя которого указано в конструкторе атрибута и имеет следующую подпись:

public static XmlQualifiedName GetSchemaMethod(XmlSchemaSet xs) 
    { 
    } 

Цель этого метода двояка:

  1. Он должен заполнить XmlSchemaSet с ожидаемой схемой при сериализации экземпляров данного типа. По результатам тестирования я обнаружил, что он должен быть заполнен чем-то действительным. Он не может быть просто пустым, иначе будет выбрано исключение.

    (я не знаю, в какой степени XmlSerializer фактически проверяет против схемы при сериализации. Метод также получает вызывается при экспорте информации схемы с помощью xsd.exe.)

  2. Она должна возвращать имя типа XML для типа ,

    Похоже, что Microsoft выбрасывает исключение, которое вы видите: поскольку поставщик атрибутов схемы должен возвращать имя типа, атрибут XmlType будет в конфликте.

Таким образом, если я изменить свой Person класс следующим образом:

[XmlSchemaProvider("GetSchemaMethod")] 
public class Person : IXmlSerializable 
{ 
    // Private state 
    private string personName; 

    // Constructors 
    public Person(string name) 
    { 
     personName = name; 
    } 

    public Person() 
    { 
     personName = null; 
    } 

    // This is the method named by the XmlSchemaProviderAttribute applied to the type. 
    public static XmlQualifiedName GetSchemaMethod(XmlSchemaSet xs) 
    { 
     string EmployeeSchema = @"<?xml version=""1.0"" encoding=""utf-16""?> 
<xs:schema elementFormDefault=""qualified"" xmlns:xs=""http://www.w3.org/2001/XMLSchema""> 
    <xs:import namespace=""http://www.w3.org/2001/XMLSchema"" /> 
    <xs:element name=""Employee"" nillable=""true"" type=""Employee"" /> 
    <xs:complexType name=""Employee"" mixed=""true""> 
    <xs:sequence> 
    <xs:any /> 
    </xs:sequence> 
    </xs:complexType> 
</xs:schema>"; 

     using (var textReader = new StringReader(EmployeeSchema)) 
     using (var schemaSetReader = System.Xml.XmlReader.Create(textReader)) 
     { 
      xs.Add("", schemaSetReader); 
     } 
     return new XmlQualifiedName("Employee"); 
    } 

    // Xml Serialization Infrastructure 
    public void WriteXml(XmlWriter writer) 
    { 
     writer.WriteString(personName); 
    } 

    public void ReadXml(XmlReader reader) 
    { 
     reader.MoveToContent(); 
     var isEmpty = reader.IsEmptyElement; 
     reader.ReadStartElement(); 
     if (!isEmpty) 
     { 
      personName = reader.ReadContentAsString(); 
      reader.ReadEndElement(); 
     } 
    } 

    public XmlSchema GetSchema() 
    { 
     return (null); 
    } 

    // Print 
    public override string ToString() 
    { 
     return (personName); 
    } 
} 

И сериализовать List<Person> в XML, я получаю следующий результат:

<ArrayOfEmployee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
    <Employee>First</Employee> 
    <Employee>Second</Employee> 
    <Employee>Third</Employee> 
</ArrayOfEmployee> 

Как вы можете видеть, Имя типа XML для Person было успешно определено.

Однако вы хотите динамически переопределять имя типа XML для Person через XmlAttributeOverrides, а не установить его в типе компиляции. Для этого было бы необходимо указать XmlSchemaProviderAttribute внутри XmlAttributes. К сожалению, нет XmlSchemaProvider свойство находится внутри XmlAttributes. Похоже, Microsoft никогда не реализовывала такую ​​функциональность. Таким образом, если вы хотите продолжить этот проект, вам нужно будет сделать что-то kludgy: временно переопределить возврат GetSchemaMethod() при создании переопределяющего сериализатора.Две вещи, которые нужно иметь в виду:

  1. Под капотом, XmlSerializer работает путем создания динамической сборки. Если вы построите XmlSerializer с new XmlSerializer(Type) или new XmlSerializer(Type, String), то .Net будет кэшировать сборку и повторно использовать ее, когда сериализатор будет построен второй раз.

    Таким образом, попытка временного переопределения возврата GetSchemaMethod() при построении сериализатора с использованием любого из них приведет к сбою или непредвиденным результатам.

  2. В противном случае динамические узлы являются не кэшируется и поэтому код необходимо кэша сериализатора вручную или иметь серьезные утечки ресурсов. См. Memory Leak using StreamReader and XmlSerializer.

    В этих случаях временное переопределение возврата GetSchemaMethod() может работать.

Все это, как говорится, ниже производит XML вам требуется:

[XmlSchemaProvider("GetSchemaMethod")] 
public class Person : IXmlSerializable 
{ 
    // Private state 
    private string personName; 

    // Constructors 
    public Person(string name) 
    { 
     personName = name; 
    } 

    public Person() 
    { 
     personName = null; 
    } 

    [ThreadStatic] 
    static string personXmlTypeName; 

    const string defaultXmlTypeName = "Person"; 

    static string PersonXmlTypeName 
    { 
     get 
     { 
      if (personXmlTypeName == null) 
       personXmlTypeName = defaultXmlTypeName; 
      return personXmlTypeName; 
     } 
     set 
     { 
      personXmlTypeName = value; 
     } 
    } 

    public static IDisposable PushXmlTypeName(string xmlTypeName) 
    { 
     return new PushValue<string>(xmlTypeName,() => PersonXmlTypeName, val => PersonXmlTypeName = val); 
    } 

    // This is the method named by the XmlSchemaProviderAttribute applied to the type. 
    public static XmlQualifiedName GetSchemaMethod(XmlSchemaSet xs) 
    { 
     string EmployeeSchemaFormat = @"<?xml version=""1.0"" encoding=""utf-16""?> 
      <xs:schema elementFormDefault=""qualified"" xmlns:xs=""http://www.w3.org/2001/XMLSchema""> 
       <xs:import namespace=""http://www.w3.org/2001/XMLSchema"" /> 
       <xs:element name=""{0}"" nillable=""true"" type=""{0}"" /> 
       <xs:complexType name=""{0}"" mixed=""true""> 
       <xs:sequence> 
       <xs:any /> 
       </xs:sequence> 
       </xs:complexType> 
      </xs:schema>"; 
     var EmployeeSchema = string.Format(EmployeeSchemaFormat, PersonXmlTypeName); 

     using (var textReader = new StringReader(EmployeeSchema)) 
     using (var schemaSetReader = System.Xml.XmlReader.Create(textReader)) 
     { 
      xs.Add("", schemaSetReader); 
     } 
     return new XmlQualifiedName(PersonXmlTypeName); 
    } 

    // Xml Serialization Infrastructure 
    public void WriteXml(XmlWriter writer) 
    { 
     writer.WriteString(personName); 
    } 

    public void ReadXml(XmlReader reader) 
    { 
     reader.MoveToContent(); 
     var isEmpty = reader.IsEmptyElement; 
     reader.ReadStartElement(); 
     if (!isEmpty) 
     { 
      personName = reader.ReadContentAsString(); 
      reader.ReadEndElement(); 
     } 
    } 

    public XmlSchema GetSchema() 
    { 
     return (null); 
    } 

    // Print 
    public override string ToString() 
    { 
     return (personName); 
    } 
} 

public struct PushValue<T> : IDisposable 
{ 
    Action<T> setValue; 
    T oldValue; 

    public PushValue(T value, Func<T> getValue, Action<T> setValue) 
    { 
     if (getValue == null || setValue == null) 
      throw new ArgumentNullException(); 
     this.setValue = setValue; 
     this.oldValue = getValue(); 
     setValue(value); 
    } 

    #region IDisposable Members 

    // By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class. 
    public void Dispose() 
    { 
     if (setValue != null) 
      setValue(oldValue); 
    } 

    #endregion 
} 

public static class PersonEmployeeListSerializerFactory 
{ 
    static Dictionary<Tuple<string, string>, XmlSerializer> serializers; 
    static object padlock = new object(); 

    static PersonEmployeeListSerializerFactory() 
    { 
     serializers = new Dictionary<Tuple<string, string>, XmlSerializer>(); 
    } 

    public static XmlSerializer GetSerializer(string rootName, string personName) 
    { 
     lock (padlock) 
     { 
      XmlSerializer serializer; 
      var key = Tuple.Create(rootName, personName); 
      if (!serializers.TryGetValue(key, out serializer)) 
      { 
       using (Person.PushXmlTypeName(personName)) 
       { 
        var lOverrides = new XmlAttributeOverrides(); 
        //var lAttributes = new XmlAttributes(); 
        //lOverrides.Add(typeof(Person), lAttributes); 

        serializers[key] = serializer = new XmlSerializer(typeof(List<Person>), lOverrides, new Type[0], new XmlRootAttribute(rootName), null); 
       } 
      } 
      return serializer; 
     } 
    } 
} 

Затем сделайте

var lSerialiser = PersonEmployeeListSerializerFactory.GetSerializer("Employees", "Employee"); 

var lNamespaces = new XmlSerializerNamespaces(); 
lNamespaces.Add("", ""); 

var sb = new StringBuilder(); 
using (var writer = new StringWriter(sb)) 
    lSerialiser.Serialize(writer, lPersonList, lNamespaces); 

Console.WriteLine(sb); 

Но как вы можете видеть, что это намного сложнее, чем при использовании суррогат показанный первоначально.

Образец fiddle, показывающий оба варианта.