XmlSerializer with new enum values
We use xml serialization / deserialization extensively in our project to pass data between multiple application. We have a common xsd that we genera开发者_JAVA技巧te c# classes from then use XmlSerializer to go from xml to objects and back.
The problem we are having is when one app is updated to add new enum values but the other app is not updated yet. Now the app that is not updated tries to deserialize the xml and fails because it doesn't know about the new enum.
If we have app1 and app2, things are working correctly in the field, then app2 is update with a new enum value in the xsd and updated to the client in the field. Suddenly app1 breaks because it doesn't know about the enum, app1 might not even use that enum field, has not effect on app1, but it still breaks.
Are there any known ways around this. Basically what i want to do is define what do do when an enum is not found, use a default value or if the enum as a nullible type and set it to null.
Both XmlSerializer and DataContractSerializer throw exceptions is this situation.
I've looked at the custom xml serialization project YAXLib (http://www.codeproject.com/KB/XML/yaxlib.aspx) this also throws an exception but there is source code and can be changed. This project use different property attributes and would require quite a bit of change but is probably doable.
Any other suggestions.
Unfortunately there's no way to control how enum values are deserialized... As a workaround, you could serialize the enum values as string :
[XmlIgnore]
public MyEnum MyProperty { get; set; }
[XmlElement("MyProperty")]
public string MyPropertyAsString
{
get
{
return EnumToString(MyProperty);
}
set
{
MyProperty = StringToEnum<MyEnum>(value);
}
}
public T StringToEnum<T>(string stringValue)
{
// Manually convert the string to enum, ignoring unknown values
}
public string EnumToString<T>(T enumValue)
{
// Convert the enum to a string
}
For future reference, the best method I think is to use XmlEnumAttribute which tells the XMLSerializer what name each enum value has for serialization and deserialization.
public enum EmployeeStatus
{
[XmlEnum(Name = "Single")]
One,
[XmlEnum(Name = "Double")]
Two,
[XmlEnum(Name = "Triple")]
Three
}
I've been wrestling with this issue as well, and I've found a partial solution using XmlAttributeOverrides. Per the MS documentation:
You can override the Name property value of an XmlEnumAttribute by creating an instance of the XmlEnumAttribute class and assigning it to the XmlEnum property of an XmlAttributes object. For details, see the XmlAttributeOverrides class.
So I did this:
XmlEnumAttribute enumAttribute = new XmlEnumAttribute();
enumAttribute.Name = "new value";
XmlAttributes attributes = new XmlAttributes();
attributes.XmlEnum = enumAttribute;
XmlAttributeOverrides attributeOverrides = new XmlAttributeOverrides();
attributeOverrides.Add(typeof(EnumType), "OldValue", attributes);
XmlSerializer serializer = new XmlSerializer(typeof(MyType), attributeOverrides);
FileStream fs = new FileStream(filename, FileMode.Open);
MyType newObj = (MyType)serializer.Deserialize(fs);
The only issue I've found with this approach is I can't specify multiple values for a single enum...
I've recently ran into this same issue with the DataContractSerializer. Basically, the enum deserialization process is unforgiving in this regard making it nearly impossible to manage backward compatibility.
As a work-around, I decided to use a backing field and handle the enum conversion myself.
[DataMember(Name="AddressType")]
private string _addressType { get; set; }
public AddressType AddressType
{
get
{
AddressType result;
Enum.TryParse(_addressType, out result);
return result;
}
}
The private field can be deserialized by the DataContractSerializer, but the XmlSerializer would require a public field.
You can make the producing application aware of the multiple versions of the consuming applications and have it use different namespaces, both XML and C#, for each of the versions. There needs to be some coordination up front between the applications to agree on which schema they will be following and there isadditional responsibility on the producing application to continue to be backward compatible with all possible active consumer applications.
use c# custom serialization with versioning of the objects; this would allow you to handle the various situations that arise when one app is updated and the other isn't
Better way is to share serialization/deserialization code between 2 apps But if it is not possible you can use XmlAttributeOverrides to pretty widely control how xml will be deserialized. For example, below some old version of xml we want to deserialized
...
<Shape xsi:type="Shape">
<Style>1</Style>
<Shape/>
...
In new version style property is encoded as enum of type Style
public enum Style
{
Style1,
Style2,
Style3,
}
If we try to deserialize old xml we will get exception. We can use XmlAttributeOverrides to avoid this
var overrides = new XmlAttributeOverrides();
overrides.Add(typeof(Style), "Style1", new XmlAttributes { XmlEnum = new
XmlEnumAttribute("1" ) } );
overrides.Add(typeof(Style), "Style2", new XmlAttributes { XmlEnum = new
XmlEnumAttribute("2") });
overrides.Add(typeof(Style), "Style3", new XmlAttributes { XmlEnum = new
XmlEnumAttribute("3") });
var xmlSerializer = new XmlSerializer(typeof(ContentRootType),
overrides);
var content = xmlSerializer.Deserialize(xmlReader) as ContentRootType;
精彩评论