使用空格反序列化带标记的枚举会导致SerializationException

时间:2015-03-06 12:30:49

标签: c# wcf datacontractserializer

当反序列化使用包含空格的值的EnumMemberAttribute修饰的标记枚举时,将抛出SerializationException。值中的空格被视为分隔符。

有没有办法更改分隔符或将值放在引号中?或者是否有更简单的解决方案?

我正在考虑的选项是:

  • 使用此枚举类型的列表替换带标记的枚举
  • 用下划线替换空格
  • 这是在WCF服务中使用的,我是 意识到某些人在数据交换中的枚举被认为是一件坏事。 所以我也在考虑一起丢失枚举。

但我真的觉得这应该是可配置的东西或其他人已经解决的东西。但我找不到任何东西。

我把问题归结为一个简单的单元测试。以下代码导致:

  

消息=无效的枚举值“测试”无法反序列化为“UnitTests.TestEnum”类型。确保存在必要的枚举值,并且如果类型具有DataContractAttribute属性,则使用EnumMemberAttribute属性标记。     源= System.Runtime.Serialization

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Xml;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTests
{
    [TestClass]
    public class EnumSerizalizationTests
    {
        [TestMethod]
        public void SerializingAndDesrializingAFlaggedEnumShouldResultInSameEnumValues()
        {
            //Arrange
            var orgObject = new TestClass { Value = TestEnum.TestValue1 | TestEnum.TestValue2 };
            //Act
            var temp = DataContractSerializeObject(orgObject);
            var newObject = DataContractDeSerializeObject<TestClass>(temp);

            //Assert
            newObject.ShouldBeEquivalentTo(orgObject, "Roundtripping serialization should result in same value");
        }

        public string DataContractSerializeObject<T>(T objectToSerialize)
        {
            using (var output = new StringWriter())
            {
                using (var writer = new XmlTextWriter(output) {Formatting = Formatting.Indented})
                {
                    new DataContractSerializer(typeof (T)).WriteObject(writer, objectToSerialize);
                    return output.GetStringBuilder().ToString();
                }
            }
        }

        public T DataContractDeSerializeObject<T>(string stringToDeSerialize)
        {
            DataContractSerializer ser = new DataContractSerializer(typeof(T));
            T result;
            using (StringReader stringReader = new StringReader(stringToDeSerialize))
            {
                using (XmlReader xmlReader = XmlReader.Create(stringReader))
                {
                    result = (T)ser.ReadObject(xmlReader);
                }
            }
            return result;
        }

    }

    [DataContract]
    [KnownType(typeof(TestEnum))]
    public class TestClass
    {
        [DataMember]
        public TestEnum Value { get; set; }
    }

    [Flags]
    [DataContract]
    public enum TestEnum
    {
        [EnumMember(Value = "Test value one")]
        TestValue1 = 1,
        [EnumMember(Value = "Test value two")]
        TestValue2 = 2,
        [EnumMember]
        TestValue3 = 4,
        [EnumMember]
        TestValue4 = 8,
    }


}

1 个答案:

答案 0 :(得分:5)

您不能在值中使用空格,因为DataContractSerializer使用它并且它是硬编码的。请参阅sourcepost。但是如果你真的想在单词之间使用空格,那么使用列出的解决方案之一:

第一种方式。在值中使用其他空格字符,例如每个空间三个空格。但是你会遇到另一个问题 - 值之间没有可视分隔符。

<TestClass xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication">
  <Value>Test value one Test value two</Value>
</TestClass>

第二种方式是使用IDataContractSurrogate。这种方式将产生下面列出的XML:

<TestClass xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication">
  <Value i:type="EnumValueOfTestEnum9cBcd6LT">Test value one, Test value two</Value>
</TestClass>

它是如何工作的?我们将在序列化过程中包装枚举,并在反序列化的情况下解包。为此,我们应该使用IDataContractSurrogate

new DataContractSerializerSettings()
{
    DataContractSurrogate = new EnumSurrogate(),
    KnownTypes = new Type[] { typeof(EnumValue<TestEnum>) }
};

public class EnumSurrogate : IDataContractSurrogate
{
    #region IDataContractSurrogate Members

    public object GetCustomDataToExport(Type clrType, Type dataContractType)
    {
        return null;
    }

    public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
    {
        return null;
    }

    public Type GetDataContractType(Type type)
    {
        return type;
    }

    public object GetDeserializedObject(object obj, Type targetType)
    {
        IEnumValue enumValue = obj as IEnumValue;

        if (enumValue!= null)
        { return enumValue.Value; }

        return obj;
    }

    public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
    {
    }

    public object GetObjectToSerialize(object obj, Type targetType)
    {
        if (obj != null)
        {
            Type type = obj.GetType();

            if (type.IsEnum && Attribute.IsDefined(type, typeof(FlagsAttribute)))
            { return Activator.CreateInstance(typeof(EnumValue<>).MakeGenericType(type), obj); }
        }

        return obj;
    }

    public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
    {
        return null;
    }

    public CodeTypeDeclaration ProcessImportedType(CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit)
    {
        return null;
    }

    #endregion
}

public interface IEnumValue : IXmlSerializable
{
    object Value { get; }
}

[Serializable]
public class EnumValue<T> : IEnumValue
    where T : struct
{
    #region Fields

    private Enum value;
    private static Type enumType;
    private static long[] values;
    private static string[] names;
    private static bool isULong;

    #endregion

    #region Constructors

    static EnumValue()
    {
        enumType = typeof(T);

        if (!enumType.IsEnum)
        { throw new InvalidOperationException(); }

        FieldInfo[] fieldInfos = enumType.GetFields(BindingFlags.Static | BindingFlags.Public);

        values = new long[fieldInfos.Length];
        names = new string[fieldInfos.Length];
        isULong = Enum.GetUnderlyingType(enumType) == typeof(ulong);

        for (int i = 0; i < fieldInfos.Length; i++)
        {
            FieldInfo fieldInfo = fieldInfos[i];
            EnumMemberAttribute enumMemberAttribute = (EnumMemberAttribute)fieldInfo
                .GetCustomAttributes(typeof(EnumMemberAttribute), false)
                .FirstOrDefault();
            IConvertible value = (IConvertible)fieldInfo.GetValue(null);

            values[i] = (isULong)
                ? (long)value.ToUInt64(null)
                : value.ToInt64(null);
            names[i] = (enumMemberAttribute == null || string.IsNullOrEmpty(enumMemberAttribute.Value))
                ? fieldInfo.Name
                : enumMemberAttribute.Value;
        }
    }

    public EnumValue()
    {
    }

    public EnumValue(Enum value)
    {
        this.value = value;
    }

    #endregion

    #region IXmlSerializable Members

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        string stringValue = reader.ReadElementContentAsString();

        long longValue = 0;
        int i = 0;

        // Skip initial spaces
        for (; i < stringValue.Length && stringValue[i] == ' '; i++) ;

        // Read comma-delimited values
        int startIndex = i;
        int nonSpaceIndex = i;
        int count = 0;

        for (; i < stringValue.Length; i++)
        {
            if (stringValue[i] == ',')
            {
                count = nonSpaceIndex - startIndex + 1;

                if (count > 1)
                { longValue |= ReadEnumValue(stringValue, startIndex, count); }

                nonSpaceIndex = ++i;

                // Skip spaces
                for (; i < stringValue.Length && stringValue[i] == ' '; i++) ;

                startIndex = i;

                if (i == stringValue.Length)
                { break; }
            }
            else
            {
                if (stringValue[i] != ' ')
                { nonSpaceIndex = i; }
            }
        }

        count = nonSpaceIndex - startIndex + 1;

        if (count > 1)
            longValue |= ReadEnumValue(stringValue, startIndex, count);

        value = (isULong)
            ? (Enum)Enum.ToObject(enumType, (ulong)longValue)
            : (Enum)Enum.ToObject(enumType, longValue);
    }

    public void WriteXml(XmlWriter writer)
    {
        long longValue = (isULong)
            ? (long)((IConvertible)value).ToUInt64(null)
            : ((IConvertible)value).ToInt64(null);

        int zeroIndex = -1;
        bool noneWritten = true;

        for (int i = 0; i < values.Length; i++)
        {
            long current = values[i];

            if (current == 0)
            {
                zeroIndex = i;
                continue;
            }

            if (longValue == 0)
            { break; }

            if ((current & longValue) == current)
            {
                if (noneWritten)
                { noneWritten = false; }
                else
                { writer.WriteString(","); }

                writer.WriteString(names[i]);
                longValue &= ~current;
            }
        }

        if (longValue != 0)
        { throw new InvalidOperationException(); }

        if (noneWritten && zeroIndex >= 0)
        { writer.WriteString(names[zeroIndex]); }
    }

    #endregion

    #region IEnumValue Members

    public object Value
    {
        get { return value; }
    }

    #endregion

    #region Private Methods

    private static long ReadEnumValue(string value, int index, int count)
    {
        for (int i = 0; i < names.Length; i++)
        {
            string name = names[i];

            if (count == name.Length && string.CompareOrdinal(value, index, name, 0, count) == 0)
            { return values[i]; }
        }

        throw new InvalidOperationException();
    }

    #endregion
}

第三种方式是动态发出类,如果基类具有标记的Enum属性,则用string属性替换它们,并使用生成的类的实例作为代理