2015-03-06 3 views
9

Beim Deserialisieren einer gekennzeichneten Enumeration, die mit einem EnumMemberAttribute mit einem Wert versehen ist, der ein Leerzeichen enthält, wird eine SerializationException ausgelöst. Das Leerzeichen im Wert wird als Trennzeichen behandelt.Das Deserialisieren einer gekennzeichneten Enumeration mit einem Leerzeichen führt zu SerializationException

Gibt es eine Möglichkeit, das Trennzeichen zu ändern oder die Werte in Anführungszeichen zu setzen? Oder gibt es sogar eine einfachere Lösung?

Optionen Ich bin schon in Betracht ziehen:

  • Ersetzen des markierten Enum mit einer Liste dieser Aufzählungstyp
  • Ersetzen der Räume mit Unterstrichen
  • Dieses in einem WCF-Dienst verwendet wird, und ich bin bewusst, dass enums in datacontracts von einigen als eine schlechte Sache betrachtet werden. Also denke ich auch darüber nach, die Enum alle zusammen zu verlieren.

Aber ich fühle wirklich, dass dies etwas konfigurierbar sein sollte oder etwas, das andere Leute bereits gelöst haben. Aber ich kann nichts finden.

Ich habe das Problem auf einen einfachen Komponententest gekocht. Der folgende Code ergibt:

Message = Ungültige Enum-Wert '-Test' kann nicht deserialisiert in Typ 'UnitTests.TestEnum' werden. Stellen Sie sicher, dass die erforderlichen Enum-Werte vorhanden sind und mit dem EnumMemberAttribute-Attribut gekennzeichnet sind, wenn der Typ über das DataContractAttribute-Attribut verfügt. Source = 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, 
    } 


} 
+2

Ich würde nicht sagen, dass „Aufzählungen in Datacontracts eine schlechte Sache sind“, aber für mich „die Verwendung von Leerzeichen in Aufzählungen ist eine schlechte Sache“ ;-) – fixagon

+0

Vereinbarte :) Ich mag keine Leerzeichen überall, aber das ist nicht meine Wahl . Wenn ich dir die Werte zeigen würde, die in den tatsächlichen enums gehen müssen, würdest du anfangen zu weinen. – KeesDijk

+0

schau dir das auch an: http://stackoverflow.com/questions/1415140/can-my-enums-have-friendly-names, hilft aber nicht wirklich in deiner Situation. Andernfalls können Sie die Enumeration einfach als int serialisieren. aber das bricht den Datenkontrakt – fixagon

Antwort

5

Sie nicht Raum in Werte verwenden können, weil DataContractSerializer es verwendet und es ist hartcodiert. Siehe die source und die post. Aber wenn Sie wirklich Platz zwischen Wörtern verwenden möchten, dann verwenden Sie eine der aufgelisteten Lösungen:

Der erste Weg. Verwenden Sie andere Leerzeichen, z. B. Drei-Prozent-Leerzeichen in Werten. Aber Sie werden ein anderes Problem haben - es gibt keinen visuellen Trenner zwischen den Werten.

<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> 

Der zweite Weg istIDataContractSurrogate zu verwenden. Auf diese Weise wird das unten aufgeführte XML erzeugt:

<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> 

Wie funktioniert es? Wir werden unsere Enumeration einfach in den Prozess der Serialisierung und des Auspackens im Falle einer Deserialisierung einschließen. Um das zu tun, dass wir IDataContractSurrogate verwenden sollen:

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 
} 

Der dritte Weg ist dynamisch die Klasse zu emittieren, wenn die Basisklasse Enum Eigenschaften gekennzeichnet ist, ersetzen Sie sie durch string Eigenschaften und Instanzen der generierten Klasse verwenden, wie Surrogate.

+0

Vielen Dank für Ihre ausführliche Antwort! Wenn ich das hartcodierte Trennzeichen sehe, bin ich sicher genug, dass mir keine einfache Option fehlt. Ihre zweite Option sieht interessant aus, ich sehe noch nicht alle Auswirkungen, die sich daraus ergeben. Ich werde das ein bisschen besser anschauen und sehen, ob es sich lohnt. – KeesDijk

+0

Ich habe die 'EnumValue ' Klassenimplementierung abgeschlossen. Und du kannst es jetzt benutzen. –

Verwandte Themen