2012-11-30 6 views
6

Ich versuche, eine generische Funktion zu erstellen, die bei Angabe eines Enum-Typs ein Objekt zurückgibt, das bei der Serialisierung durch WebApi eine gut aussehende Ausgabe als XML/Json liefert.Serialisierung eines dynamischen Typs in der Web-API

Diese Methode funktioniert einwandfrei, wenn sie als JSON serialisiert wird, aber ich kann nicht mit XML arbeiten. Wenn ich das zurückgegebene Objekt manuell mit einem XmlSerializer oder einem DataContractSerializer serialisiert, erhalte ich die erwarteten Ergebnisse. Wenn WebAPI selbst versucht es auf der anderen Seite von einem Httprequest serialisiert werden, erhalte ich Fehler wie folgt aus:

System.Runtime.Serialization.SerializationException

Typ ‚Priorität‘ mit Datenvertragsnamen ‚Priorität : http: //schemas.datacontract.org/2004/07/ 'wird nicht erwartet. Erwägen Sie, einen DataContractResolver zu verwenden oder alle nicht bekannten Typen statisch zu der Liste der bekannten Typen hinzuzufügen, z. B. indem Sie das KnownTypeAttribute-Attribut verwenden oder sie der Liste bekannter Typen hinzufügen, die an DataContractSerializer übergeben werden.

Ich habe mit GlobalConfiguration.Configuration.Formatters.XmlFormatter.SetSerializer versucht, den Serializer für den erzeugten Typen festgelegt, dass ich Werke von Setzen von Breakpoints wissen, aber es scheint, nur um es zu ignorieren und wirft die gleiche Ausnahme. Die Enums werden durch Ganzzahlen unterstützt und garantieren für jeden Eintrag eindeutige Werte. Hier ist der Code, den ich verwende, um den Typ zu generieren und eine Instanz davon zurückzugeben.

public object GetSerializableEnumProxy(Type enumType) { 

    if (enumType == null) { 
     throw new ArgumentNullException("enumType"); 
    } 

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

    AssemblyName assemblyName = new AssemblyName("DataBuilderAssembly"); 
    AssemblyBuilder assemBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); 
    ModuleBuilder moduleBuilder = assemBuilder.DefineDynamicModule("DataBuilderModule"); 
    TypeBuilder typeBuilder = moduleBuilder.DefineType(enumType.Name, TypeAttributes.Class | TypeAttributes.Public); 

    // Add the [DataContract] attribute to our generated type 
    typeBuilder.SetCustomAttribute(
     new CustomAttributeBuilder(typeof(DataContractAttribute).GetConstructor(Type.EmptyTypes), new object[] {}) 
    ); 

    CustomAttributeBuilder dataMemberAttributeBuilder = new CustomAttributeBuilder(
     typeof(DataMemberAttribute).GetConstructor(Type.EmptyTypes), new object[] {} 
    ); 

    // For each name in the enum, define a corresponding public int field 
    // with the [DataMember] attribute 
    foreach (var value in Enum.GetValues(enumType).Cast<int>()) { 
     var name = Enum.GetName(enumType, value); 

     var fb = typeBuilder.DefineField(name, typeof(int), FieldAttributes.Public); 

     // Add the [DataMember] attribute to the field 
     fb.SetCustomAttribute(dataMemberAttributeBuilder); 

     // Set the value of our field to be the corresponding value from the Enum 
     fb.SetConstant(value); 
    }  

    // Return an instance of our generated type 
    return Activator.CreateInstance(typeBuilder.CreateType()); 
} 

Web Api-Controller-Methode:

private static IEnumerable<Type> RetrievableEnums = new Type[] { 
    typeof(Priority), typeof(Status) 
}; 

[GET("enum/{enumName}")] 
public HttpResponseMessage GetEnumInformation(string enumName) { 

    Type enumType = RetrievableEnums.SingleOrDefault(type => 
     String.Equals(type.Name, enumName, StringComparison.InvariantCultureIgnoreCase)); 

    if (enumType == null) { 
     return Request.CreateErrorResponse(HttpStatusCode.NotFound, "The requested enum could not be retrieved"); 
    } 

    return Request.CreateResponse(HttpStatusCode.OK, GetSerializableEnumProxy(enumType)); 
} 

Irgendwelche Ideen?

+0

Können Sie eine Web-API-Methode einbeziehen, die diesen Fehler bei der Rückgabe von XML reproduziert? Ich denke, ich weiß, was das Problem ist, aber ich muss sehen, wie Sie versuchen, dieses Enum als Inhalt zurückzugeben. –

+0

@AndrasZoltan Bearbeitete meine ursprüngliche Frage mit einer Beispiel-Web-API-Methode – dherman

+0

, wie ich vermutete - Übergabe des Objekts in den Inhalt als ein "Objekt" - ich kann aus der Akzeptanz sehen, dass die vorgeschlagene Lösung funktioniert :) –

Antwort

8

Ich glaube, das ist letztlich, weil Sie den Enum-Wert als object senden - und im Gegensatz zum Json-Formatierer, Web XML-Formatierer, der DataContractSerializer verwendet, verwendet (in der Tat) die Kompilierzeit Art des Werts sein serialisiert, nicht der Laufzeittyp.

Als Ergebnis müssen Sie immer sicherstellen, dass alle abgeleiteten Typen einer Basis, die Sie versuchen zu serialisieren, zu den bekannten Typen des zugrunde liegenden Serialisierungsprogramms hinzugefügt werden. In diesem Fall haben Sie die dynamische Enum (was natürlich eine object ist).

Auf dem Gesicht von ihm, so scheint es, dass der SetSerializer(type, serializer) Ansatz funktionieren soll, aber ich werde Sie es mit dem dynamischen Typ als erstes Argument Aufruf Wette - was, wenn Sie die Enum sind senden, wird nicht funktionieren out als object - weil es der object Serializer ist, den die XmlRequestFormatter verwenden wird.

Dies ist ein bekanntes Problem - und ein which I've reported as an issue on codeplex (es gibt ein gutes Beispiel, das das Problem in einem einfacheren Szenario zeigt).

Dieses Problem enthält auch einige C# -Code für ein Attribut und Ersatz für XmlMediaTypeFormatter (genannt XmlMediaTypeFormatterEx), die eine Lösung für dieses Problem bietet - es verwendet eine deklarative pro-Operation-Ansatz.Ersetzen Sie die XmlMediaTypeFormatter mit dem in dem Code - so etwas wie diese verwenden (beachten Sie, dieser Code den Fall behandelt, wo es keine XML-Formatierungsprogramm ist bereits definiert - vielleicht etwas sinnlos):

var configuration = GlobalConfiguration.Configuration; 
var origXmlFormatter = configuration.Formatters.OfType<XmlMediaTypeFormatter>() 
         .SingleOrDefault(); 

XmlMediaTypeFormatterEx exXmlFormatter = new XmlMediaTypeFormatterEx(origXmlFormatter); 

if (origXmlFormatter != null) 
{ 
    configuration.Formatters.Insert(
     configuration.Formatters.IndexOf(origXmlFormatter), exXmlFormatter); 
    configuration.Formatters.Remove(origXmlFormatter); 
} 
else 
    configuration.Formatters.Add(exXmlFormatter); 

Und jetzt auf der API-Methode, die Sie wollen diese Dynamik enum zurückkehren Sie es mit diesem schmücken würde:

[XmlUseReturnedUnstanceType] 
public object Get() 
{ 

} 

Nun, unabhängig von der Art Sie von der Get Methode zurückgeben, die einen benutzerdefinierten Formatierer Kicks in und DataContractSerializer speziell für den Laufzeittyp verwendet, nicht object.

Dies behandelt nicht Aufzählungen oder Wörterbücher von Basen - es wird dann sehr kompliziert - aber für grundlegende Single-Instance-Rückgabewerte funktioniert es gut.

Verwandte Themen