Je travaille actuellement sur une intégration entre les systèmes et j'ai décidé d'utiliser WebApi pour cela, mais je rencontre un problème ...
Disons que j'ai un modèle:
public class TestModel
{
public string Output { get; set; }
}
et la méthode POST est:
public string Post(TestModel model)
{
return model.Output;
}
Je crée une demande de Fiddler avec l'en-tête:
User-Agent: Fiddler
Content-Type: "application/xml"
Accept: "application/xml"
Host: localhost:8616
Content-Length: 57
et le corps:
<TestModel><Output>Sito</Output></TestModel>
Le paramètre model
dans la méthode Post
est toujours null
et je ne sais pas pourquoi. Quelqu'un a-t-il une idée?
Deux choses:
Vous n'avez pas besoin de guillemets ""
autour du type de contenu et acceptez les valeurs d'en-tête dans Fiddler:
User-Agent: Fiddler
Content-Type: application/xml
Accept: application/xml
L'API Web utilise le DataContractSerializer
par défaut pour la sérialisation xml. Vous devez donc inclure l'espace de noms de votre type dans votre xml:
<TestModel
xmlns="http://schemas.datacontract.org/2004/07/YourMvcApp.YourNameSpace">
<Output>Sito</Output>
</TestModel>
Ou vous pouvez configurer l'API Web pour utiliser XmlSerializer
dans votre WebApiConfig.Register
:
config.Formatters.XmlFormatter.UseXmlSerializer = true;
Ensuite, vous n'avez pas besoin de l'espace de noms dans vos données XML:
<TestModel><Output>Sito</Output></TestModel>
Bien que la réponse soit déjà attribuée, j'ai trouvé quelques autres détails qui méritent d'être examinés.
L'exemple le plus élémentaire d'une publication XML est généré dans le cadre d'un nouveau projet WebAPI automatiquement par Visual Studio, mais cet exemple utilise une chaîne comme paramètre d'entrée.
Exemple de contrôleur WebAPI simplifié généré par Visual Studio
using System.Web.Http;
namespace webAPI_Test.Controllers
{
public class ValuesController : ApiController
{
// POST api/values
public void Post([FromBody]string value)
{
}
}
}
Ce n'est pas très utile, car cela ne répond pas à la question qui se pose. La plupart des services Web POST ont des types plutôt complexes comme paramètres, et probablement un type complexe comme réponse. J'augmenterai l'exemple ci-dessus pour inclure une demande complexe et une réponse complexe ...
échantillon simplifié mais avec ajout de types complexes
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; }
}
}
À ce stade, je peux invoquer avec un violoneux ..
Détails de la demande du violoneux
En-têtes de demande:
User-Agent: Fiddler
Host: localhost:54842
Content-Length: 63
Organe de demande:
<MyRequest>
<Age>99</Age>
<Name>MyName</Name>
</MyRequest>
... et lorsque je place un point d'arrêt dans mon contrôleur, je trouve que l'objet de demande est nul. C'est à cause de plusieurs facteurs ...
Sans apporter de modifications au contrôleur de service Web, je peux modifier la demande du violon afin qu'elle fonctionne. Portez une attention particulière aux définitions d'espace de noms dans le corps de requête xml POST. Assurez-vous également que la déclaration XML est incluse avec les paramètres UTF corrects qui correspondent à l'en-tête de la requête.
Correction du corps de la requête Fiddler pour travailler avec les types de données complexes
En-têtes de demande:
User-Agent: Fiddler
Host: localhost:54842
Content-Length: 276
Content-Type: application/xml; charset=utf-16
Organe de demande:
<?xml version="1.0" encoding="utf-16"?>
<MyRequest xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/webAPI_Test.Controllers">
<Age>99</Age>
<Name>MyName</Name>
</MyRequest>
Remarquez comment l'espace de noms dans la demande fait référence au même espace de noms dans ma classe de contrôleur C # (sorte de). Parce que nous n'avons pas modifié ce projet pour utiliser un sérialiseur autre que DataContractSerializer et parce que nous n'avons pas décoré notre modèle (classe MyRequest ou MyResponse) avec des espaces de noms spécifiques, il suppose le même espace de noms que le contrôleur WebAPI lui-même. Ce n'est pas très clair et c'est très déroutant. Une meilleure approche serait de définir un espace de noms spécifique.
Pour définir un espace de noms spécifique, nous modifions le modèle de contrôleur. Vous devez ajouter une référence à System.Runtime.Serialization pour que cela fonctionne.
Ajouter des espaces de noms au modèle
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; }
}
}
Maintenant, mettez à jour la demande Fiddler pour utiliser cet espace de noms ...
demande Fiddler avec espace de noms personnalisé
<?xml version="1.0" encoding="utf-16"?>
<MyRequest xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="MyCustomNamespace">
<Age>99</Age>
<Name>MyName</Name>
</MyRequest>
Nous pouvons pousser cette idée encore plus loin. Si une chaîne vide est spécifiée comme espace de noms sur le modèle, aucun espace de noms dans la demande du violon n'est requis.
Contrôleur avec espace de noms de chaîne vide
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; }
}
}
demande Fiddler sans espace de noms déclaré
<?xml version="1.0" encoding="utf-16"?>
<MyRequest>
<Age>99</Age>
<Name>MyName</Name>
</MyRequest>
Autres Gotchas
Attention, DataContractSerializer s'attend à ce que les éléments de la charge utile XML soient classés par ordre alphabétique par défaut. Si la charge utile XML est hors service, vous pouvez trouver que certains éléments sont nuls (ou si le type de données est un entier, il sera par défaut à zéro, ou s'il s'agit d'un booléen, il sera par défaut faux). Par exemple, si aucune commande n'est spécifiée et que le xml suivant est soumis ...
corps XML avec classement incorrect des éléments
<?xml version="1.0" encoding="utf-16"?>
<MyRequest>
<Name>MyName</Name>
<Age>99</Age>
</MyRequest>
... la valeur pour Age sera par défaut à zéro. Si du xml presque identique est envoyé ...
corps XML avec ordre correct des éléments
<?xml version="1.0" encoding="utf-16"?>
<MyRequest>
<Age>99</Age>
<Name>MyName</Name>
</MyRequest>
le contrôleur WebAPI sérialise et remplit correctement le paramètre Age. Si vous souhaitez modifier l'ordre par défaut afin que le XML puisse être envoyé dans un ordre spécifique, ajoutez alors l'élément 'Order' à l'attribut DataMember.
Exemple de spécification d'un ordre de propriété
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; }
}
}
Dans cet exemple, le corps xml doit spécifier l'élément Name avant l'élément Age pour se remplir correctement.
Conclusion
Ce que nous voyons, c'est qu'un corps de demande POST POST $ === malformé ou incomplet (du point de vue de DataContractSerializer) ne génère pas d'erreur, mais cause simplement un problème d'exécution. Si vous utilisez DataContractSerializer, nous devons satisfaire le sérialiseur (en particulier autour des espaces de noms). J'ai trouvé à l'aide d'un outil de test une bonne approche - où je passe une chaîne XML à une fonction qui utilise DataContractSerializer pour désérialiser le XML. Il génère des erreurs lorsque la désérialisation ne peut pas se produire. Voici le code pour les tests une chaîne XML à l'aide de DataContractSerializer (encore une fois, n'oubliez pas que si vous implémentez cela, vous devez ajouter une référence à System.Runtime.Serialization).
Exemple de code de test pour l'évaluation de la désérialisation de 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;
}
Options
Comme souligné par d'autres, DataContractSerializer est la valeur par défaut pour les projets WebAPI utilisant XML, mais il existe d'autres sérialiseurs XML. Vous pouvez supprimer le DataContractSerializer et utiliser à la place XmlSerializer. Le XmlSerializer est beaucoup plus indulgent sur les éléments d'espace de noms mal formés.
Une autre option consiste à limiter les demandes à l'utilisation de JSON au lieu de XML. Je n'ai effectué aucune analyse pour déterminer si DataContractSerializer est utilisé pendant la désérialisation JSON et si l'interaction JSON nécessite des attributs DataContract pour décorer les modèles.
J'essayais de résoudre cela pendant deux jours. Finalement, j'ai découvert que la balise externe doit être le nom du type, pas le nom de la variable. En effet, avec la méthode POST as
public string Post([FromBody]TestModel model)
{
return model.Output;
}
Je fournissais le corps
<model><Output>Sito</Output></model>
au lieu de
<TestModel><Output>Sito</Output></TestModel>
Une fois que vous vous êtes assuré d'avoir configuré le Content-Type
en-tête à application/xml
Et mettre config.Formatters.XmlFormatter.UseXmlSerializer = true;
dans la méthode Register
de WebApiConfig.cs
, il est important de ne pas avoir besoin de version ou d'encodage en haut de votre document XML.
Cette dernière pièce me bloquait, j'espère que cela aide quelqu'un et vous fait gagner du temps.