Генерация двух разных сериализаций XML для класса .net - PullRequest
2 голосов
/ 12 октября 2010

У меня есть набор классов .net, которые я в настоящее время сериализую и использую с кучей другого кода, поэтому формат этого xml относительно фиксирован (формат # 1).Мне нужно сгенерировать XML в другом формате (формат № 2), который имеет довольно похожую структуру, но не совсем то же самое, и мне интересно, как лучше всего это сделать.

Например, скажем, что это мои классы:

public class Resource 
{ 
    public string Name { get; set; }
    public string Description { get; set; }
    public string AnotherField { get; set; }
    public string AnotherField2 { get; set; }
    public Address Address1 { get; set; }
    public Address Address2 { get; set; }
    public Settings Settings { get; set; }
}
public class Address
{ 
    public string Line1 { get; set; }
    public string Line2 { get; set; }
    public string City { get; set; }
} 
// This class has custom serialization because it's sort-of a dictionary. 
// (Maybe that's no longer needed but it seemed necessary back in .net 2.0).
public class Settings : IXmlSerializable
{ 
    public string GetSetting(string settingName) { ... }
    public string SetSetting(string settingName, string value) { ... }
    public XmlSchema GetSchema() { return null; }
    public void ReadXml(XmlReader reader) 
    {
        // ... reads nested <Setting> elements and calls SetSetting() appropriately 
    }
    public void WriteXml(XmlWriter writer)
    {
        // ... writes nested <Setting> elements 
    }
} 

Я обычно использую стандартную XmlSerialization, и она производит отличный XML (формат # 1).Примерно так:

<Resource>
  <Name>The big one</Name>
  <Description>This is a really big resource</Description>
  <AnotherField1>ADVMW391</AnotherField1>
  <AnotherField2>green</AnotherField2>
  <Address1>
    <Line1>1 Park Lane</Line1>
    <Line2>Mayfair</Line2>
    <City>London</City>
  </Address1>
  <Address2>
    <Line1>11 Pentonville Rd</Line1>
    <Line2>Islington</Line2>
    <City>London</City>
  </Address2>
  <Settings>
    <Setting>
      <Name>Height</Name>
      <Value>12.4</Value>
    </Setting>
    <Setting>
      <Name>Depth</Name>
      <Value>14.1028</Value>
    </Setting>
  </Settings>
</Resource>

Новый XML, который я хочу сгенерировать (формат # 2), выглядит как текущий XML, за исключением:

  • Вместо поля AnotherFieldи AnotherField2, теперь они должны быть представлены как Настройки.то есть, как если бы SetSetting () был вызван дважды перед сериализацией, поэтому значения появляются как новые элементы внутри.

  • Вместо полей Address1 и Address2 они должны быть представлены какэлемент, содержащий два элемента.У элементов должен быть дополнительный атрибут или два, например Position и AddressType.

например

<Resource>
  <Name>The big one</Name>
  <Description>This is a really big resource</Description>
  <Addresses>
    <Address>
      <Line1>1 Park Lane</Line1>
      <Line2>Mayfair</Line2>
      <City>London</City>
      <Position>1</Position>
      <AddressType>Postal</AddressType>
    </Address>
    <Address>
      <Line1>11 Pentonville Rd</Line1>
      <Line2>Islington</Line2>
      <City>London</City>
      <Position>2</Position>
      <AddressType>Postal</AddressType>
    </Address>
  </Addresses>
  <Settings>
    <Setting>
      <Name>Height</Name>
      <Value>12.4</Value>
    </Setting>
    <Setting>
      <Name>Depth</Name>
      <Value>14.1028</Value>
    </Setting>
    <Setting>
      <Name>AnotherField</Name>
      <Value>ADVMW391</Value>
    </Setting>
    <Setting>
      <Name>AnotherField2</Name>
      <Value>green</Value>
    </Setting>
  </Settings>
</Resource>

Могу ли я использовать XmlAttributeOverrides для управления сериализацией таким образом?Иначе как мне подойти?

Имейте в виду, что мои реальные классы имеют по крайней мере в 10 раз больше полей, и есть некоторые вложенные классы, где я полностью доволен сериализацией по умолчанию, поэтому я хотел бы избежать слишком большого количества ручногокод сериализации.

Возможные параметры

Я вижу следующие параметры:

  1. Возможно, можно использовать переопределения для управления сериализацией только тех атрибутов, которые мне нужныоколо?
  2. Пользовательская сериализация класса Resource для формата # 2, при необходимости вызывающая сериализацию вложенных классов по умолчанию.Не знаю, как обращаться с настройками, так как я действительно хочу добавить настройки, сериализовать с использованием параметров по умолчанию, затем удалить добавленные настройки.
  3. Создать XML с использованием сериализации по умолчанию, а затем манипулировать XML, чтобы внести необходимые изменения.(ick!).

Другое небольшое осложнение заключается в том, что Resource из моего примера выше имеет два подтипа, каждый с парой дополнительных полей.Сериализация по умолчанию обрабатывает это приятно.Любой новый метод также должен иметь дело с сериализацией этих подтипов.Это означает, что я не заинтересован в решении, которое подразумевает создание разных подтипов исключительно для целей сериализации.

Ответы [ 2 ]

2 голосов
/ 23 октября 2010

В итоге я решил эту проблему, создав новый класс, который выполнял настраиваемую сериализацию атрибутов, которые в этом нуждались, и затем использовал XmlAttributeOverrides, чтобы убедиться, что класс используется вместо сериализации по умолчанию для атрибута.

public class Resource
{ 
    ...

    // the method that actually does the serialization
    public void SerializeToFormat2Xml(XmlWriter writer)
    { 
        Format2Serializer.Serialize(writer, this);
    }

    // Cache the custom XmlSerializer. Since we're using overrides it won't be cached
    // by the runtime so if this is used frequently it'll be a big performance hit
    // and memory leak if it's not cached. See docs on XmlSerializer for more.
    static XmlSerializer _format2Serializer = null;
    static XmlSerializer Format2Serializer
    { 
        get { 
            if (_format2Serializer == null) 
            { 
                XmlAttributeOverrides overrides = new XmlAttributeOverrides();
                XmlAttributes ignore = new XmlAttributes();
                ignore.XmlIgnore = true;

                // ignore serialization of fields that will go into Settings 
                overrides.Add(typeof (Resource), "AnotherField", ignore);
                overrides.Add(typeof (Resource), "AnotherField2", ignore);

                // instead of serializing the normal Settings object, we use a custom serializer field
                overrides.Add(typeof (Resource), "Settings", ignore);
                XmlAttributes attributes = new XmlAttributes();
                attributes.XmlIgnore = false;
                attributes.XmlElements.Add(new XmlElementAttribute("Settings"));
                overrides.Add(typeof (Resource), "CustomSettingsSerializer", attributes);

                // ... do similar stuff for Addresses ... not in this example


                _format2Serializer = new XmlSerializer(typeof(Resource), overrides);
           }
           return _format2Serializer;
        }
    }

    // a property only used for custom serialization of settings
    [XmlIgnore]
    public CustomSerializeHelper CustomSettingsSerializer
    {
        get { return new CustomSerializeHelper (this, "Settings"); }
        set { } // needs setter otherwise won't be serialized!
    }

    // would have a similar property for custom serialization of addresses, 
    // defaulting to XmlIgnore.
}


public class CustomSerializeHelper : IXmlSerializable
{
    // resource to serialize
    private Resource _resource;

    // which field is being serialized. 
    private string _property;

    public CustomSerializeHelper() { } // must have a default constructor
    public CustomSerializeHelper(Resource resource, string property)
    {
        _resource = resource;  
        _property = property;  
    }

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        return;
    }

    public void WriteXml(XmlWriter writer)
    {
        if (_property == "Settings")
        {
            Dictionary<string, string> customSettings = new Dictionary<string, string>();
            customSettings.Add("AnotherField", _resource.AnotherField);
            customSettings.Add("AnotherField2", _resource.AnotherField2);

            _resource.Settings.WriteXml(writer, customSettings);
        }
        if (_property == "Addresses")
        { 
            // ... similar custom serialization for Address, 
            // in that case getting a new XmlSerializer(typeof(Address)) and calling
            // Serialize(writer,Address), with override to add Position.
        }
    }


public partial class Settings
{ 
    // added this new method to Settings so it can serialize itself plus
    // some additional settings.
    public void WriteXml(XmlWriter writer, Dictionary<string, string> additionalSettingsToWrite)
    {
        WriteXml(writer);
        foreach (string key in additionalSettingsToWrite.Keys)
        {
            string value = additionalSettingsToWrite[key];
            writer.WriteStartElement("Setting");
            writer.WriteElementString("SettingType", key);
            writer.WriteElementString("SettingValue", value);
            writer.WriteEndElement();
        }
    }

}
1 голос
/ 12 октября 2010

UPDATE:
Как уже отмечали другие, мой подход ниже также может быть реализован с меньшими усилиями с помощью XmlAttributeOverrides. Я по-прежнему буду публиковать свой ответ как еще один способ создания 2 разных тегов XML для обычно унаследованного свойства, но я также рекомендую вам изучить XmlAttributeOverrides.

ОРИГИНАЛЬНЫЙ ОТВЕТ:
Если вы попытаетесь решить эту проблему простым методом наследования родитель-потомок, я ожидаю, что вы быстро столкнетесь с проблемами с XmlSerializer.

Что вы должны сделать (IMHO) - сделать текущий класс (ы) базовым (и) и установить для атрибута XmlElement значение XmlIgnore (для полей, которые вы хотите изменить). Этот набор базовых классов должен содержать всю необходимую логику получения / установки.

Разветвление наследства на 2 набора детей. Один набор должен быть простым набором, который изменит XmlIgnore на [XmlElement] (нет необходимости указывать ElementName для этого набора). Это все, что этот класс (ы) намеревался сделать.

Второй набор будет наследоваться от базового класса и изменит XmlIgnore на [XmlElement(ElementName=myNameHere)] для тех же полей, о которых идет речь. Это все, что нужно этому классу.

Вот пример для иллюстрации того, о чем я говорю:

Базовый класс:

public class OriginalClass
{
    private string m_field;

    [XmlIgnore]
    public virtual string Field 
    {
        get
        {
            return m_field;
        }
        set
        {
            m_field = value;
        }
    }
}

Детский класс (1):

public class ChildClass : OriginalClass
{
    public ChildClass() { }

    [XmlElement]
    public override string Field
    {
        get { return base.Field; }
        set { base.Field = value; }
    }
}

Дочерний класс (2) - тот, который переопределяет имя поля:

public class ChildClass2 : OriginalClass
{
    public ChildClass2() { }

    [XmlElement(ElementName = "NewField")]
    public override string Field
    {
        get { return base.Field; }
        set { base.Field = value; }
    }
}

Пример программы:

class Program
{
    static void Main(string[] args)
    {
        ChildClass obj1 = new ChildClass();
        ChildClass2 obj2 = new ChildClass2();

        obj1.Field = "testing overridden field";
        obj2.Field = "testing overridden field (2)";


        var sw = new StreamWriter(Console.OpenStandardOutput());

        XmlSerializer xs = new XmlSerializer(typeof(ChildClass));
        xs.Serialize(sw, obj1);
        Console.WriteLine();

        XmlSerializer xs2 = new XmlSerializer(typeof(ChildClass2));
        xs2.Serialize(sw, obj2);

        Console.ReadLine();
    }
}

В выводе XML для ChildClass2 будет указано «NewField».

...