Bug In the .NET CompactFramework XmlEnum with Whitespace

December 21, 2006 - 4 minute read -

In .NET you can use Attributes to mark up properties in a class to tell the XmlSerializer how to marshal that class to and from XML. There are a number of different attributes you can use. You can use enums for restricted lists of values.

E.g:

public enum Foo {
    [XmlEnum("Some Value")
    Some,
    [XmlEnum("Another")]
    Other</p>
}

To represent something like Some Value

At least that's the idea... It seems like XmlEnum marked values with whitespace in their names will not be deserialized on the .NET CF 2.0. It throws an InvalidOperationException saying it can't find the enum with value of the part of the string before the space.

I would expect it to deserialize into the enum Foo.Some. All of this works on the Full Framework.

Workaround

If you control the schema you can workaround this issue by changing the allowed values not to have spaces in them. But in many cases you might be trying to integrate with an existing service or schema that you do not control and as such, you won't have the ability to change it. If that's the case what do you do?

Luckily I found some code to help me get the XmlEnumAttribute value for an Enum field (thanks for sharing).

I made some slight modifications to it, so that it was a little safer (I think).

/// <summary>
/// Convert an Enum value to a string representation using the XmlEnum value if
/// it is appropriate.
/// </summary>
public static string ConvertToString(Enum e)
{
    if (null == e)
        throw new ArgumentNullException("Enum type can not be null.");
      // Get the Type of the enum
    Type t = e.GetType();
      // Get the FieldInfo for the member field with the enums name
    FieldInfo info = t.GetField(e.ToString("G"));
      // Check to see if the XmlEnumAttribute is defined on this field
    if (null != info && info.IsDefined(typeof(XmlEnumAttribute), false))
    {
        // Get the XmlEnumAttribute
        object[] attrs = info.GetCustomAttributes(typeof(XmlEnumAttribute), false);
        if (attrs.Length > 0)
        {
            XmlEnumAttribute att = (XmlEnumAttribute)attrs[0];
            return att.Name;
        }
    }
      // If no XmlEnumAttribute was found then return the string version of the enum.
    return e.ToString("G");
}

I also needed to go the other way then, and convert a String back into an Enum so that I can support deserializing from XML into the object model.

/// <summary>
/// Get an Enum value for a given type of Enum based on the String name of the Enum
/// itself or the XmlEnum element that it has.
/// </summary>
public static Enum ConvertToEnum(Type enumType, string name)
{
    // Look for an XmlEnumAttribute with the given name
    foreach (FieldInfo fi in enumType.GetFields())
    {
        object[] attrs = fi.GetCustomAttributes(typeof(XmlEnumAttribute), false);
        if ((attrs.Length > 0 && ((XmlEnumAttribute)attrs[0]).Name == name )
            || fi.Name == name)
            return (Enum) Enum.Parse(enumType, fi.Name, false);
    }
<p>    return null;
}

Using this code, you can offer an API to client code that uses the strongly-typed Enum, but does a manual conversion for the serialization process.

class Bar {
    private Foo fooType;
    // The code called by the application
    [XmlIgnore]
    public Foo FooType {
        get { return fooType; }
        set { fooType = value; }
    }
    // The code used to manage serialization
    // Ideally this could be private, but that doesn't work with the serializer
    [XmlElement("baz")]
    public string FooTypeForSerializer {
        get { return ConvertToString(fooType); }
        set { fooType = (Foo) ConvertToEnum(typeof(Foo), value);
    }
}

Conclusion

One big workaround for what seems to me to be a bug. But it works, and I can continue to get work done. It should also be easy to refactor this workaround out if the bug ever gets fixed. Just change the [XmlIgnore] tag and delete the ForSerializer methods.

I hope that helps if you run into a similar problem.