为.net类生成两个不同的xml序列化

时间:2010-10-11 22:41:46

标签: .net serialization xml-serialization

我有一组.net类,我目前正在序列化并与一堆其他代码一起使用,因此该xml的格式相对固定(格式#1)。我需要以另一种格式(格式#2)生成xml,这种格式非常相似,但不完全相同,我想知道最好的方法。

例如,说这些是我的课程:

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,除了:

  • 现在应将这些字段表示为“设置”,而不是字段AnotherFieldAnotherField2。即好像SetSetting()在序列化之前被调用了两次,因此这些值在其中显示为新元素。

  • 而不是字段Address1Address2,它们应该表示为包含两个元素的元素。元素应该具有一个或多个属性,例如位置和地址类型。

e.g。

<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. 自定义格式#2的Resource类的序列化,在适当的位置调用嵌套类的默认序列化。不确定如何处理设置,因为我实际上想要添加设置,使用默认序列化,然后删除添加的设置。
  3. 使用默认序列化创建xml,然后操纵XML以进行我需要的更改。 (ICK!)。
  4. 另一个轻微的复杂问题是,上面示例中的Resource实际上有两个子类型,每个子类型都有一些额外的字段。默认序列化处理得很好。任何新方法都需要处理这些子类型的序列化。这意味着我并不热衷于一个解决方案,它涉及我为了序列化目的而制作不同的子类型。

2 个答案:

答案 0 :(得分:2)

我最终通过创建一个新类来解决这个问题,该类执行需要它的属性的自定义序列化,然后使用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 :(得分:1)

<强>更新
正如其他人所指出的,使用XmlAttributeOverrides可以更轻松地实现下面的方法。我仍然会将我的答案保留为另一种为常用继承属性发出2个不同XML标记的方法,但我建议您同时查看XmlAttributeOverrides。

原文回答:
如果您尝试使用简单的父子继承方法解决此问题,我预计您会很快遇到XmlSerializer的问题。

你应该做什么(恕我直言)是将当前类放入基类,并将XmlElement属性设置为XmlIgnore(对于你想要修改的字段)。该基类集应包含所有必需的getter / setter逻辑。

将继承分为两组子项。一组应该是一个天真的集合,它会将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();
    }
}

ChildClass2的XML输出将显示为“NewField”。