web-dev-qa-db-fra.com

web-api POST objet du corps toujours nul

J'apprends encore l'API Web, alors excusez-moi si ma question semble stupide.

J'ai ceci dans ma StudentController:

public HttpResponseMessage PostStudent([FromBody]Models.Student student)
        {
            if (DBManager.createStudent(student) != null)
                return Request.CreateResponse(HttpStatusCode.Created, student);
            else
                return Request.CreateResponse(HttpStatusCode.BadRequest, student);
        }

Afin de vérifier si cela fonctionne, j'utilise l'extension "Postman" de Google Chrome pour créer la demande HTTP POST afin de le tester.

Voici ma demande POST brute:

POST /api/Student HTTP/1.1
Host: localhost:1118
Content-Type: application/json
Cache-Control: no-cache

{"student": [{"name":"John Doe", "age":18, "country":"United States of America"}]}

"student" est censé être un objet, mais lorsque je débogue l'application, l'API reçoit l'objet étudiant, mais le contenu est toujours NULL.

53
InnovativeDan

FromBody est un attribut étrange en ce sens que les valeurs d'entrée POST doivent être dans un format spécifique pour que le paramètre soit non nul, lorsqu'il ne s'agit pas d'un type primitif. (étudiant ici) 

  1. Essayez votre demande avec {"name":"John Doe", "age":18, "country":"United States of America"} comme json. 
  2. Supprimez l'attribut [FromBody] et essayez la solution. Cela devrait fonctionner pour les types non primitifs. (étudiant)
  3. Avec l'attribut [FromBody], l'autre option consiste à envoyer les valeurs au format =Value, plutôt qu'au format key=value. Cela voudrait dire que votre clé student devrait être une chaîne vide ... 

Il existe également d'autres options pour écrire un classeur de modèle personnalisé pour la classe d'étudiants et l'attribuer au paramètre avec votre classeur personnalisé.

39
Raja Nadar

Je cherchais une solution à mon problème depuis quelques minutes maintenant, je vais donc partager ma solution. 

Votre modèle doit avoir un constructeur vide/par défaut, sinon le modèle ne peut pas être créé, évidemment . Soyez prudent lors du refactoring. ;)

43
chrjs

Je passe plusieurs heures avec ce problème ... :( Les ​​getters et les setters sont OBLIGATOIRES dans la déclaration d'objet de paramètres POST. Je ne recommande pas d'utiliser de simples objets de données (string, int, ...) car ils nécessitent un format de requête spécial .

[HttpPost]
public HttpResponseMessage PostProcedure(EdiconLogFilter filter){
...
}

Ne fonctionne pas quand:

public class EdiconLogFilter
{
    public string fClientName;
    public string fUserName;
    public string fMinutes;
    public string fLogDate;
}

Fonctionne bien quand:

public class EdiconLogFilter
{
    public string fClientName { get; set; }
    public string fUserName { get; set; }
    public string fMinutes { get; set; }
    public string fLogDate { get; set; }
}
22
Milan Švec

C'est un peu vieux et ma réponse ira à la dernière place mais je voudrais quand même partager mon expérience.

Essayé chaque suggestion mais ayant toujours la même valeur "null" dans un PUT [FromBody].

Finalement, nous avons trouvé que tout était une question de format de date et que JSON sérialisait la propriété EndDate de mon objet angulaire.

Aucune erreur n'a été générée, un objet FromBody vide vient juste d'être reçu ....

13
Josep Alacid

Si l'une des valeurs de l'objet JSON de la demande n'est pas du même type que celui attendu par le service, l'argument [FromBody] sera null.

Par exemple, si la propriété age du json a une valeur float:

"âge": 18,0

mais le service API s'attend à ce qu'il s'agisse d'une int

"age": 18 ans

alors student sera null. (Aucun message d'erreur ne sera envoyé dans la réponse sauf s'il n'y a pas de vérification de référence nulle).

12
Ray Vega

Si vous utilisez Postman, assurez-vous que:

  • Vous avez défini un en-tête "Content-Type" sur "application/json"
  • Vous envoyez le corps comme "brut"
  • Vous n'avez pas besoin de spécifier le nom du paramètre n'importe où si vous utilisez [FromBody]

J'essayais bêtement d'envoyer mon JSON en tant que données de formulaire, duh ...

7
John M

TL; DR: n'utilisez pas [FromBody], mais lancez votre propre version de celle-ci avec une meilleure gestion des erreurs. Raisons données ci-dessous.}

D'autres réponses décrivent de nombreuses causes possibles de ce problème. Cependant, la cause première est que [FromBody] a simplement une terrible gestion des erreurs, ce qui le rend presque inutile dans le code de production.

Par exemple, l'une des raisons les plus courantes pour que le paramètre soit null est que le corps de la demande a une syntaxe non valide (par exemple, JSON non valide). Dans ce cas, une API raisonnable renverrait 400 BAD REQUEST, et un framework Web raisonnable le ferait automatiquement. Cependant, l’API Web ASP.NET n’est pas raisonnable à cet égard. Il définit simplement le paramètre sur null et le gestionnaire de demandes a alors besoin d'un code "manuel" pour vérifier si le paramètre est null.

La plupart des réponses données ici sont donc incomplètes en ce qui concerne la gestion des erreurs, et un client buggué ou malveillant peut provoquer un comportement inattendu du côté serveur en envoyant une requête non valide, ce qui (dans le meilleur des cas) jettera un NullReferenceException quelque part et retournera l'état incorrect de 500 INTERNAL SERVER ERROR ou, pire, faire quelque chose d'inattendu, de planter ou d'exposer une vulnérabilité de sécurité.

Une solution appropriée serait d'écrire un attribut personnalisé "[FromBody]" qui gère correctement les erreurs et renvoie les codes d'état appropriés, idéalement avec des informations de diagnostic pour aider les développeurs clients.

Une solution qui pourrait aider (pas encore testé) est de rendre les paramètres obligatoires, comme suit: https://stackoverflow.com/a/19322688/2279059

La solution maladroite suivante fonctionne également:

// BAD EXAMPLE, but may work reasonably well for "internal" APIs...

public HttpResponseMessage MyAction([FromBody] JObject json)
{
  // Check if JSON from request body has been parsed correctly
  if (json == null) {
    var response = new HttpResponseMessage(HttpStatusCode.BadRequest) {
      ReasonPhrase = "Invalid JSON"
    };
    throw new HttpResponseException(response);
  }

  MyParameterModel param;
  try {
    param = json.ToObject<MyParameterModel>();
  }
  catch (JsonException e) {
    var response = new HttpResponseMessage(HttpStatusCode.BadRequest) {
      ReasonPhrase = String.Format("Invalid parameter: {0}", e.Message)
    };
    throw new HttpResponseException(response);
  }

  // ... Request handling goes here ...

}

Ceci (espérons-le) gère correctement les erreurs, mais est moins déclaratif. Si, par exemple, vous utilisez Swagger pour documenter votre API, elle ne connaît pas le type de paramètre, ce qui signifie que vous devez rechercher une solution de contournement manuelle pour documenter vos paramètres. Ceci est juste pour illustrer ce que [FromBody] devrait faire.

EDIT: Une solution moins lourde consiste à vérifier ModelState: https://stackoverflow.com/a/38515689/2279059

EDIT: Il semble que ModelState.IsValid ne soit pas, comme on pouvait s'y attendre, défini sur false si vous utilisez JsonProperty avec Required = Required.Always et qu'un paramètre est manquant. Donc, cela est également inutile.

Cependant, à mon avis, toute solution nécessitant l'écriture de code supplémentaire dans chaque gestionnaire de demandes est inacceptable. Dans un langage tel que .NET, doté de puissantes fonctionnalités de sérialisation, et dans un framework tel que l’API Web ASP.NET, la validation des demandes doit être automatique et intégrée, et elle est tout à fait réalisable, même si Microsoft ne fournit pas les fonctionnalités intégrées nécessaires. outils.

5
Florian Winter

J'essayais aussi d'utiliser [FromBody], cependant, j'essayais de renseigner une variable de chaîne car l'entrée changerait et je devais simplement la transmettre à un service principal, mais cette valeur était toujours nulle

Post([FromBody]string Input]) 

J'ai donc changé la signature de la méthode pour utiliser une classe dynamique, puis la convertir en chaîne.

Post(dynamic DynamicClass)
{
   string Input = JsonConvert.SerializeObject(DynamicClass);

Ça marche bien.

3
Mike

Juste pour ajouter mon histoire à ce sujet . Mon modèle:

public class UserProfileResource
{
    public Guid Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Phone { get; set; }

    public UserProfileResource()
    {
    }
}

ne peut pas être sérialisé dans mon contrôleur API, renvoyant toujours la valeur null. Le problème était avec un identifiant de type Guid : chaque fois que j'ai passé une chaîne vide sous forme d'un identifiant (étant naïf, il sera automatiquement converti en Guid.Empty) depuis mon interface, j'ai reçu un objet null sous la forme [FromBody] paramether. 

La solution était plus rapide 

  • passer la valeur Guid valide 
  • ou remplacez Guid par String
1
Dariusz

Il peut être utile d’ajouter TRACING au sérialiseur json pour que vous puissiez voir ce qui se passe quand tout va mal.

Définissez une implémentation ITraceWriter pour afficher la sortie de débogage comme suit:

class TraceWriter : Newtonsoft.Json.Serialization.ITraceWriter
{
    public TraceLevel LevelFilter {
        get {
            return TraceLevel.Error;
        }
    }

    public void Trace(TraceLevel level, string message, Exception ex)
    {
        Console.WriteLine("JSON {0} {1}: {2}", level, message, ex);
    }
}

Ensuite, dans votre WebApiConfig, faites:

    config.Formatters.JsonFormatter.SerializerSettings.TraceWriter = new TraceWriter();

(peut-être envelopper dans un #Si DEBUG)

1
Phuo

Dans mon cas, le problème était l’objet DateTime que j’envoyais. J'ai créé une DateTime avec "aaaa-MM-jj", et la DateTime requise par l'objet que je mappais sur nécessaire "HH-mm-ss" ainsi. Ainsi, l’ajout du code "00-00" a résolu le problème (l’élément complet était nul pour cette raison).

1
shocks

J'ai eu le même problème. 

Dans mon cas, le problème était dans la propriété public int? CreditLimitBasedOn { get; set; } que j'avais. 

mon JSON avait la valeur "CreditLimitBasedOn":true quand il devait contenir un entier. Cette propriété a empêché la désérialisation de tout l'objet sur ma méthode api.

1
Romesh D. Niriella

Peut-être que cela sera utile pour quelqu'un: vérifiez les modificateurs d'accès pour les propriétés de votre classe DTO/Model, ils devraient être public . Dans mon cas, lors du refactoring, les éléments internes des objets de domaine ont été déplacés vers DTO comme suit:

// Domain object
public class MyDomainObject {
    public string Name { get; internal set; }
    public string Info { get; internal set; }
}
// DTO
public class MyDomainObjectDto {
    public Name { get; internal set; } // <-- The problem is in setter access modifier (and carelessly performed refactoring).
    public string Info { get; internal set; }
}

DTO est finement transmis au client, mais lorsque le moment est venu de renvoyer l'objet au serveur, il ne contenait que des champs vides (valeur null/par défaut). Supprimer "interne" met les choses en ordre, permettant au mécanisme de désérialisation d'écrire les propriétés de l'objet.

public class MyDomainObjectDto {
    public Name { get; set; }
    public string Info { get; set; }
}
1
ddolgushin

Vérifiez si l'attribut JsonProperty est défini sur les champs dont la valeur est NULL - il est possible qu'ils soient mappés sur différents noms de propriété JSON.

1
Bashir Magomedov

J'ai utilisé HttpRequestMessage et mon problème a été résolu après de nombreuses recherches.

[HttpPost]
public HttpResponseMessage PostProcedure(HttpRequestMessage req){
...
}
1
aejaz ahmad

J'ai rencontré ce problème très souvent, mais en réalité, il est assez simple de rechercher la cause.

Voici l'exemple d'aujourd'hui. J'appelais mon service POST avec un objet AccountRequest, mais lorsque je plaçais un point d'arrêt au début de cette fonction, la valeur du paramètre était toujours null. Mais pourquoi?!

[ProducesResponseType(typeof(DocumentInfo[]), 201)]
[HttpPost]
public async Task<IActionResult> Post([FromBody] AccountRequest accountRequest)
{
    //  At this point...  accountRequest is null... but why ?!

    //  ... other code ... 
}

Pour identifier le problème, définissez le type de paramètre sur string, ajoutez une ligne pour obtenir JSON.Net afin de désérialiser l'objet dans le type que vous attendiez et placez un point d'arrêt sur cette ligne:

[ProducesResponseType(typeof(DocumentInfo[]), 201)]
[HttpPost]
public async Task<IActionResult> Post([FromBody] string ar)
{
    //  Put a breakpoint on the following line... what is the value of "ar" ?
    AccountRequest accountRequest = JsonConvert.DeserializeObject<AccountRequest>(ar);

    //  ... other code ...
}

Maintenant, lorsque vous essayez ceci, si le paramètre est toujours vide ou null, alors vous n’appelez pas le service correctement.

Toutefois, si la chaîne contient-elle une valeur, la variable DeserializeObject devrait vous indiquer la cause du problème et ne devrait pas non plus convertir votre chaîne au format souhaité. Mais avec les données brutes (string) qu’il tente de désérialiser, vous devriez maintenant être en mesure de voir ce qui ne va pas avec la valeur de votre paramètre.

(Dans mon cas, nous appelions le service avec un objet AccountRequest qui avait été accidentellement sérialisé deux fois!)

0
Mike Gledhill

J'ai rencontré ce problème dans mon API Web .NET Framework, car mon modèle existait dans un projet .NET Standard faisant référence à une version différente des annotations de données.

L'ajout de la ligne ReadAsAsync ci-dessous a mis en évidence la cause pour moi:

public async Task<HttpResponseMessage> Register(RegistrationDetails registrationDetails)
{
    var regDetails = await Request.Content.ReadAsAsync<RegistrationDetails>();
0
kraeg

Si cela est dû au fait que Web API 2 a rencontré un problème de désérialisation en raison de types de données incohérents, il est possible de connaître son échec en inspectant le flux de contenu. Il lira jusqu'à ce qu'il rencontre une erreur. Ainsi, si vous lisez le contenu sous forme de chaîne, vous devriez avoir la moitié arrière des données que vous avez publiées:

string json = await Request.Content.ReadAsStringAsync();

Corrigez ce paramètre, et il devrait aller plus loin la prochaine fois (ou réussir si vous êtes chanceux!) ...

0
Jereme

Après trois jours de recherche et aucune des solutions ci-dessus ne fonctionnaient pour moi, j'ai trouvé une autre approche de ce problème dans ce lien: HttpRequestMessage

J'ai utilisé l'une des solutions de ce site 

[HttpPost]
public async System.Threading.Tasks.Task<string> Post(HttpRequestMessage request)
{
    string body = await request.Content.ReadAsStringAsync();
    return body;
}
0
Farid Amiri

On dirait qu'il peut y avoir de nombreuses causes différentes à ce problème ...

J'ai constaté que l'ajout d'un rappel OnDeserialized à la classe de modèle faisait que le paramètre était toujours null. Raison exacte inconnue.

using System.Runtime.Serialization;

// Validate request
[OnDeserialized]  // TODO: Causes parameter to be null
public void DoAdditionalValidatation() {...}
0
Florian Winter