2012-11-30 14 views
6

Sto tentando di creare una funzione generica che quando viene fornito un tipo Enum restituirà un oggetto che, una volta serializzato da WebApi, fornirà un output di aspetto gradevole come XML/Json.Serializzare un tipo dinamico nell'API Web

Questo metodo funziona perfettamente quando serializzato come JSON, ma non riesco a farlo funzionare con XML. Se serializzo manualmente l'oggetto restituito con un XmlSerializer o DataContractSerializer, ottengo i risultati come previsto. Quando WebAPI si tenta di serializzare esso d'altra parte da un HttpRequest, ricevo errori come il seguente:

System.Runtime.Serialization.SerializationException

Tipo 'priorità' con il nome del contratto di dati 'prioritario : http: //schemas.datacontract.org/2004/07/ 'non è previsto. Considerare l'utilizzo di DataContractResolver o aggiungere staticamente i tipi non noti all'elenco dei tipi noti, ad esempio utilizzando l'attributo KnownTypeAttribute o aggiungendoli all'elenco dei tipi noti passati a DataContractSerializer.

Ho provato con GlobalConfiguration.Configuration.Formatters.XmlFormatter.SetSerializer per impostare il serializzatore per il tipo generato che conosco opere provenienti da punti di interruzione di impostazione, ma sembra solo di ignorarlo e getta la stessa eccezione. L'enumerazione sarà supportata da numeri interi e sarà garantito un valore univoco per ogni voce. Ecco il codice che sto usando per generare il tipo e restituirne un'istanza.

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 Metodo di controllo:

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)); 
} 

Tutte le idee?

+0

È possibile includere un metodo API Web che riproduce questo errore quando si restituisce XML? Penso di sapere qual è il problema, ma ho bisogno di vedere come stai cercando di restituire questo enum come contenuto. –

+0

@AndrasZoltan Ho modificato la mia domanda originale con un metodo API Web di esempio – dherman

+0

come sospetto, passando l'oggetto nel contenuto come un 'oggetto'. Posso vedere dall'accettazione che la soluzione che ho suggerito funzionava :) –

risposta

8

Credo che questo sia in ultima analisi, perché si sta inviando il valore enum come object - e, a differenza del formattatore JSON, formattatore di API Web XML, che utilizza DataContractSerializer, usi (in vigore) il tipo in fase di compilazione del valore di essere serializzato, non il tipo di runtime.

Di conseguenza, è necessario sempre assicurarsi che tutti i tipi derivati ​​di una base che si sta tentando di serializzare vengano aggiunti ai tipi noti del serializzatore sottostante. In questo caso hai l'enum dinamico (che è un object, ovviamente).

Sulla faccia di esso, sembra che l'approccio SetSerializer(type, serializer) dovrebbe funzionare, tuttavia, scommetto la vostra vocazione con il tipo dinamico come primo argomento - che non funziona se si sono l'invio del enum come object - perché è il serializzatore object che verrà utilizzato da XmlRequestFormatter.

Questo è un problema ben noto - e uno which I've reported as an issue on codeplex (c'è un buon esempio che mostra il problema in uno scenario più semplice).

Questo numero comprende anche alcune codice C# per un attributo e sostituzione per XmlMediaTypeFormatter (chiamato XmlMediaTypeFormatterEx), che fornisce una soluzione a questo problema - si utilizza un approccio dichiarativo per-operazione.Sostituire il XmlMediaTypeFormatter con quello nel codice - utilizzando qualcosa di simile (si noti gestisce questo codice nel caso in cui non v'è alcuna formattatore XML già definito - forse un po 'inutilmente):

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

E ora sul metodo API che si vogliono tornare questo enum dinamica che ci si decorare con questo:

[XmlUseReturnedUnstanceType] 
public object Get() 
{ 

} 

Ora, qualunque sia il tipo si torna dal metodo Get, i calci di formattazione personalizzata in e utilizza un DataContractSerializer appositamente per il tipo di runtime, non object.

Questo non gestisce enumerable o dizionari di basi - diventa molto complicato quindi - ma per i valori di ritorno di base singola istanza funziona correttamente.

Problemi correlati