Modello sempre nullo su XML POST

Attualmente sto lavorando a un’integrazione tra i sistemi e ho deciso di utilizzare WebApi per questo, ma sto riscontrando un problema …

Diciamo che ho un modello:

public class TestModel { public string Output { get; set; } } 

e il metodo POST è:

 public string Post(TestModel model) { return model.Output; } 

Creo una richiesta da Fiddler con l’intestazione:

 User-Agent: Fiddler Content-Type: "application/xml" Accept: "application/xml" Host: localhost:8616 Content-Length: 57 

e il corpo:

 Sito 

Il parametro del model nel metodo Post è sempre null e non ho idea del perché. Qualcuno ha un indizio?

Due cose:

  1. Non hai bisogno di virgolette "" attorno al tipo di contenuto e accetta i valori di intestazione in Fiddler:

     User-Agent: Fiddler Content-Type: application/xml Accept: application/xml 
  2. L’API Web utilizza DataContractSerializer per impostazione predefinita per la serializzazione xml. Quindi devi includere lo spazio dei nomi del tuo tipo nel tuo xml:

      Sito  

    Oppure puoi configurare Web API per utilizzare XmlSerializer nel tuo WebApiConfig.Register :

     config.Formatters.XmlFormatter.UseXmlSerializer = true; 

    Quindi non è necessario lo spazio dei nomi nei dati XML:

      Sito 

Mentre la risposta è già stata premiata, ho trovato un paio di altri dettagli che vale la pena considerare.

L’esempio più semplice di un post XML viene generato come parte di un nuovo progetto WebAPI automaticamente da Visual Studio, ma in questo esempio viene utilizzata una stringa come parametro di input.

Controller WebAPI di esempio semplificato generato da Visual Studio

 using System.Web.Http; namespace webAPI_Test.Controllers { public class ValuesController : ApiController { // POST api/values public void Post([FromBody]string value) { } } } 

Questo non è molto utile, perché non affronta la domanda in questione. La maggior parte dei servizi web POST ha tipi piuttosto complessi come parametri e probabilmente un tipo complesso come risposta. Aumenterò l’esempio sopra per includere una richiesta complessa e una risposta complessa …

Campione semplificato ma con tipi complessi aggiunti

 using System.Web.Http; namespace webAPI_Test.Controllers { public class ValuesController : ApiController { // POST api/values public MyResponse Post([FromBody] MyRequest value) { var response = new MyResponse(); response.Name = value.Name; response.Age = value.Age; return response; } } public class MyRequest { public string Name { get; set; } public int Age { get; set; } } public class MyResponse { public string Name { get; set; } public int Age { get; set; } } } 

A questo punto, posso invocare con il violinista ..

Dettagli richiesta Fiddler

Richieste intestazioni:

 User-Agent: Fiddler Host: localhost:54842 Content-Length: 63 

Corpo della richiesta:

  99 MyName  

… e quando si posiziona un punto di interruzione nel mio controller, trovo che l’object richiesta è nullo. Questo a causa di diversi fattori …

  • Per impostazione predefinita, WebAPI utilizza DataContractSerializer
  • La richiesta di Fiddler non specifica il tipo di contenuto o il set di caratteri
  • Il corpo della richiesta non include la dichiarazione XML
  • Il corpo della richiesta non include le definizioni dello spazio dei nomi.

Senza apportare modifiche al controller del servizio web, posso modificare la richiesta del violinista in modo che funzioni. Presta particolare attenzione alle definizioni dello spazio dei nomi nel corpo della richiesta POST xml. Inoltre, assicurarsi che la dichiarazione XML sia inclusa con le impostazioni UTF corrette che corrispondono all’intestazione della richiesta.

Risolto il problema con il corpo della richiesta di Fiddler per lavorare con i tipi di dati complessi

Richieste intestazioni:

 User-Agent: Fiddler Host: localhost:54842 Content-Length: 276 Content-Type: application/xml; charset=utf-16 

Corpo della richiesta:

   99 MyName  

Si noti come il namepace nella richiesta si riferisca allo stesso spazio dei nomi nella mia class di controller C # (tipo di). Poiché non abbiamo modificato questo progetto per utilizzare un serializzatore diverso da DataContractSerializer e poiché non abbiamo decorato il nostro modello (class MyRequest o MyResponse) con spazi dei nomi specifici, assume lo stesso spazio dei nomi del Controller WebAPI stesso. Questo non è molto chiaro ed è molto confuso. Un approccio migliore sarebbe definire uno spazio dei nomi specifico.

Per definire uno spazio dei nomi specifico, modifichiamo il modello del controller. È necessario aggiungere un riferimento a System.Runtime.Serialization per farlo funzionare.

Aggiungi spazi dei nomi al modello

 using System.Runtime.Serialization; using System.Web.Http; namespace webAPI_Test.Controllers { public class ValuesController : ApiController { // POST api/values public MyResponse Post([FromBody] MyRequest value) { var response = new MyResponse(); response.Name = value.Name; response.Age = value.Age; return response; } } [DataContract(Namespace = "MyCustomNamespace")] public class MyRequest { [DataMember] public string Name { get; set; } [DataMember] public int Age { get; set; } } [DataContract(Namespace = "MyCustomNamespace")] public class MyResponse { [DataMember] public string Name { get; set; } [DataMember] public int Age { get; set; } } } 

Ora aggiorna la richiesta di Fiddler per usare questo spazio dei nomi …

Richiesta di Fiddler con spazio dei nomi personalizzato

   99 MyName  

Possiamo portare ancora più avanti questa idea. Se una stringa vuota viene specificata come spazio dei nomi sul modello, non è richiesto alcun spazio dei nomi nella richiesta del violinista.

Controller con spazio dei nomi stringa vuoto

 using System.Runtime.Serialization; using System.Web.Http; namespace webAPI_Test.Controllers { public class ValuesController : ApiController { // POST api/values public MyResponse Post([FromBody] MyRequest value) { var response = new MyResponse(); response.Name = value.Name; response.Age = value.Age; return response; } } [DataContract(Namespace = "")] public class MyRequest { [DataMember] public string Name { get; set; } [DataMember] public int Age { get; set; } } [DataContract(Namespace = "")] public class MyResponse { [DataMember] public string Name { get; set; } [DataMember] public int Age { get; set; } } } 

Richiesta di Fiddler senza spazio dei nomi dichiarato

   99 MyName  

Altri Gotchas

Attenzione, DataContractSerializer si aspetta che gli elementi nel payload XML vengano ordinati alfabeticamente per impostazione predefinita. Se il payload XML è fuori uso, è ansible che alcuni elementi siano nulli (o se datatype è un numero intero verrà impostato su zero, o se è un valore bool predefinito è impostato su false). Ad esempio, se non viene specificato alcun ordine e viene inviato il seguente xml …

Corpo XML con ordine errato di elementi

   MyName 99  

… il valore di Age verrà impostato su zero. Se viene inviato xml quasi identico …

Corpo XML con ordinamento corretto degli elementi

   99 MyName  

quindi il controller WebAPI serializzerà correttamente e popolerà il parametro Age. Se si desidera modificare l’ordinamento predefinito in modo che l’XML possa essere inviato in un ordine specifico, quindi aggiungere l’elemento ‘Ordine’ all’Attributo DataMember.

Esempio di specifica di un ordine di proprietà

 using System.Runtime.Serialization; using System.Web.Http; namespace webAPI_Test.Controllers { public class ValuesController : ApiController { // POST api/values public MyResponse Post([FromBody] MyRequest value) { var response = new MyResponse(); response.Name = value.Name; response.Age = value.Age; return response; } } [DataContract(Namespace = "")] public class MyRequest { [DataMember(Order = 1)] public string Name { get; set; } [DataMember(Order = 2)] public int Age { get; set; } } [DataContract(Namespace = "")] public class MyResponse { [DataMember] public string Name { get; set; } [DataMember] public int Age { get; set; } } } 

In questo esempio, il corpo xml deve specificare l’elemento Name prima che l’elemento Age sia compilato correttamente.

Conclusione

Quello che vediamo è che un corpo di richiesta POST malformato o incompleto (dal punto di vista di DataContractSerializer) non genera un errore, piuttosto è solo causa di un problema di runtime. Se si utilizza DataContractSerializer, è necessario soddisfare il serializzatore (in particolare intorno ai namespace). Ho trovato che usare uno strumento di test è un buon approccio – dove passo una stringa XML ad una funzione che usa DataContractSerializer per deserializzare l’XML. Genera errori quando la deserializzazione non può verificarsi. Ecco il codice per testare una stringa XML usando DataContractSerializer (ancora una volta, ricordati di implementarlo, devi aggiungere un riferimento a System.Runtime.Serialization).

Esempio di codice di test per la valutazione della de-serializzazione di DataContractSerializer

 public MyRequest Deserialize(string inboundXML) { var ms = new MemoryStream(Encoding.Unicode.GetBytes(inboundXML)); var serializer = new DataContractSerializer(typeof(MyRequest)); var request = new MyRequest(); request = (MyRequest)serializer.ReadObject(ms); return request; } 

Opzioni

Come sottolineato da altri, DataContractSerializer è l’impostazione predefinita per i progetti WebAPI che utilizzano XML, ma esistono altri serializzatori XML. È ansible rimuovere DataContractSerializer e utilizzare invece XmlSerializer. XmlSerializer è molto più indulgente su roba di namespace malformato.

Un’altra opzione è limitare le richieste all’utilizzo di JSON anziché XML. Non ho eseguito alcuna analisi per determinare se DataContractSerializer è utilizzato durante la deserializzazione JSON e se l’interazione JSON richiede attributi DataContract per decorare i modelli.

Stavo cercando di risolvere questo per due giorni. Alla fine ho scoperto che il tag esterno deve essere il nome del tipo, non il nome della variabile. In modo efficace, con il metodo POST come

 public string Post([FromBody]TestModel model) { return model.Output; } 

Stavo fornendo il corpo

 Sito 

invece di

 Sito 

Dopo esserti assicurato di impostare l’intestazione Content-Type su application / xml e config.Formatters.XmlFormatter.UseXmlSerializer = true; nel metodo Register di WebApiConfig.cs è importante che non sia necessario alcun controllo di versione o codifica nella parte superiore del documento XML.

Quest’ultimo pezzo mi ha bloccato, spero che questo aiuti qualcuno fuori e ti fa risparmiare tempo.