我有一组.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,除了:
现在应将这些字段表示为“设置”,而不是字段AnotherField
和AnotherField2
。即好像SetSetting()在序列化之前被调用了两次,因此这些值在其中显示为新元素。
而不是字段Address1
和Address2
,它们应该表示为包含两个元素的元素。元素应该具有一个或多个属性,例如位置和地址类型。
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倍的字段数,并且有一些嵌套类,我对默认的序列化完全满意,所以我想避免过多的手动序列化代码。
可能的选项
我可以看到这些选项:
另一个轻微的复杂问题是,上面示例中的Resource
实际上有两个子类型,每个子类型都有一些额外的字段。默认序列化处理得很好。任何新方法都需要处理这些子类型的序列化。这意味着我并不热衷于一个解决方案,它涉及我为了序列化目的而制作不同的子类型。
答案 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”。