webentwicklung-frage-antwort-db.com.de

web-API POST Körperobjekt immer null

Ich lerne immer noch die Web-API, also verzeihen Sie, wenn meine Frage dumm klingt.

Ich habe dies in meiner 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);
        }

Um zu testen, ob dies funktioniert, verwende ich die Erweiterung "Postman" von Google Chrome, um die HTTP-Anforderung POST zum Testen zu erstellen.

Dies ist meine rohe POST -Anfrage:

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" soll ein Objekt sein, aber beim Debuggen der Anwendung empfängt die API das Schülerobjekt, der Inhalt ist jedoch immer NULL.

53
InnovativeDan

FromBody ist ein merkwürdiges Attribut, da die Werte der Eingabe POST in einem bestimmten Format sein müssen, damit der Parameter nicht null ist, wenn es sich nicht um einen primitiven Typ handelt. (Student hier) 

  1. Versuchen Sie Ihre Anfrage mit {"name":"John Doe", "age":18, "country":"United States of America"} als json. 
  2. Entfernen Sie das [FromBody]-Attribut, und versuchen Sie die Lösung. Es sollte für nicht primitive Typen funktionieren. (Student)
  3. Mit dem [FromBody]-Attribut können Sie die Werte nicht im =Value-Format, sondern im key=value-Format senden. Dies würde bedeuten, dass Ihr Schlüsselwert von student eine leere Zeichenfolge sein sollte ... 

Es gibt auch andere Optionen, um einen benutzerdefinierten Modellordner für die Studentenklasse zu erstellen und den Parameter Ihrem benutzerdefinierten Ordner zuzuordnen.

39
Raja Nadar

Ich habe jetzt einige Minuten nach einer Lösung für mein Problem gesucht, also teile ich meine Lösung mit. 

Ihr Modell muss über einen leeren/Standardkonstruktor verfügen, andernfalls kann das Modell offensichtlich nicht erstellt werden .. _. Seien Sie beim Refactoring vorsichtig. ;)

43
chrjs

Ich verbringe mehrere Stunden mit diesem Problem ... :( In POST - Parameter-Objektdeklaration werden Getter und Setter ERFORDERLICH. Ich empfehle die Verwendung einfacher Datenobjekte (string, int, ...) nicht, da sie ein spezielles Anforderungsformat erfordern .

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

Funktioniert nicht, wenn:

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

Funktioniert gut, wenn:

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

Dies ist ein bisschen alt und meine Antwort wird bis zum letzten Platz gehen, aber trotzdem möchte ich meine Erfahrungen teilen.

Versuchte jeden Vorschlag, hatte aber immer noch den gleichen "Null" -Wert in einem PUT [FromBody].

Schließlich stellte sich heraus, dass alles über das Datumsformat ging, während JSON die EndDate-Eigenschaft meines Angular-Objekts serialisierte.

Es wurde kein Fehler ausgegeben, es wurde gerade ein leeres FromBody-Objekt empfangen.

13
Josep Alacid

Wenn einige Werte des JSON-Objekts der Anforderung nicht denselben Typ haben, wie er vom Service erwartet wird, lautet das [FromBody]-Argument null.

Wenn zum Beispiel die Eigenschaft age in der json einen float-Wert hatte:

"Alter": 18,0

der API-Dienst erwartet jedoch eine int

"Alter": 18

dann wird studentnull sein. (In der Antwort werden keine Fehlernachrichten gesendet, es sei denn, es wird keine Überprüfung auf Null durchgeführt.).

12
Ray Vega

Wenn Sie Postman verwenden, stellen Sie Folgendes sicher:

  • Sie haben den Header "Content-Type" auf "application/json" gesetzt.
  • Sie senden den Körper als "roh"
  • Sie müssen den Parameternamen nirgendwo angeben, wenn Sie [FromBody] verwenden.

Ich habe dumm versucht, meine JSON als Formulardaten zu senden, duh ...

7
John M

TL; DR: Verwenden Sie [FromBody] nicht, sondern rollen Sie Ihre eigene Version mit besserer Fehlerbehandlung. Gründe unten.

Andere Antworten beschreiben viele mögliche Ursachen für dieses Problem. Die Hauptursache ist jedoch, dass [FromBody] einfach eine schreckliche Fehlerbehandlung hat, was es im Produktionscode fast nutzlos macht.

Beispielsweise ist einer der typischsten Gründe dafür, dass der Parameter null ist, dass der Anforderungshauptteil eine ungültige Syntax hat (z. B. ungültige JSON). In diesem Fall würde eine sinnvolle API 400 BAD REQUEST zurückgeben, und ein vernünftiges Web-Framework würde dies automatisch tun. Die ASP.NET-Web-API ist in dieser Hinsicht jedoch nicht sinnvoll. Es setzt einfach den Parameter auf null, und der Anforderungshandler benötigt dann einen "manuellen" Code, um zu überprüfen, ob der Parameter null ist.

Viele der hier gegebenen Antworten sind daher in Bezug auf die Fehlerbehandlung unvollständig. Ein fehlerhafter oder böswilliger Client kann auf der Serverseite ein unerwartetes Verhalten verursachen, indem er eine ungültige Anforderung sendet, die (im besten Fall) irgendwo eine NullReferenceException zurückgibt und eine Antwort zurückgibt falscher Status von 500 INTERNAL SERVER ERROR oder, schlimmer noch, etwas Unerwartetes tun oder abstürzen oder eine Sicherheitsanfälligkeit aufdecken.

Eine geeignete Lösung wäre, ein benutzerdefiniertes "[FromBody]" -Attribut zu schreiben, das die korrekte Fehlerbehandlung durchführt und die richtigen Statuscodes zurückgibt, idealerweise mit einigen Diagnoseinformationen, um die Entwickler von Clients zu unterstützen.

Eine Lösung, die möglicherweise (noch nicht getestet) hilfreich sein kann, besteht darin, die folgenden Parameter festzulegen: https://stackoverflow.com/a/19322688/2279059

Die folgende ungeschickte Lösung funktioniert auch:

// 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 ...

}

Dies führt (hoffentlich) zu einer korrekten Fehlerbehandlung, ist jedoch weniger deklarativ. Wenn Sie zum Beispiel Swagger verwenden, um Ihre API zu dokumentieren, kennt sie den Parametertyp nicht. Dies bedeutet, dass Sie eine manuelle Problemumgehung finden müssen, um Ihre Parameter zu dokumentieren. Dies soll nur veranschaulichen, was [FromBody] tun soll.

BEARBEITEN: Eine weniger plumpe Lösung ist die Überprüfung von ModelState: https://stackoverflow.com/a/38515689/2279059

BEARBEITEN: Es scheint, dass ModelState.IsValid nicht, wie man erwarten würde, auf false gesetzt wird, wenn JsonProperty mit Required = Required.Always verwendet wird und ein Parameter fehlt. Das ist also auch nutzlos.

Meines Erachtens ist jedoch jede Lösung, die das Schreiben von zusätzlichem Code in every - Request-Handler erfordert, nicht akzeptabel. In einer Sprache wie .NET mit leistungsstarken Serialisierungsfunktionen und in einem Framework wie der ASP.NET-Web-API sollte die Anforderungsüberprüfung automatisch und integriert sein und ist durchaus machbar, auch wenn Microsoft das erforderliche integrierte Programm nicht bereitstellt Werkzeuge.

5
Florian Winter

Ich habe auch versucht, [FromBody] zu verwenden, habe jedoch versucht, eine String-Variable aufzufüllen, da sich die Eingabe ändern wird und ich sie nur an einen Backend-Service übergeben muss

Post([FromBody]string Input]) 

Also habe ich die Methodensignatur so geändert, dass sie eine dynamische Klasse verwendet und diese dann in eine Zeichenfolge konvertiert

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

Das funktioniert gut.

3
Mike

Nach dreitägiger Suche und keiner der oben genannten Lösungen habe ich in diesem Link einen anderen Ansatz für dieses Problem gefunden: HttpRequestMessage

Ich habe eine der Lösungen auf dieser Site verwendet

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

Nur um meine Geschichte zu diesem Thread hinzuzufügen .. Mein Modell:

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()
    {
    }
}

konnte in meinem API-Controller nicht serialisiert werden und gibt immer null zurück. Das Problem war mit der Id vom Typ Guid : Jedes Mal, wenn ich einen leeren String als Id übergeben habe (da es naiv ist, dass er automatisch in Guid.Empty konvertiert wird), erhielt ich von meinem Frontend ein Nullobjekt als [FromBody]-Parameter. 

Lösung war besser zu 

  • gültigen Guid-Wert übergeben 
  • oder Guid in String ändern
1
Dariusz

Es kann hilfreich sein, TRACING zum json Serializer hinzuzufügen, um zu sehen, was los ist, wenn etwas schief läuft.

Definieren Sie eine ITraceWriter-Implementierung, um deren Debug-Ausgabe anzuzeigen:

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

Dann in Ihrer WebApiConfig:

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

(vielleicht in ein # if DEBUG einwickeln)

1
Phuo

In meinem Fall war das Problem das DateTime-Objekt, das ich sendete. Ich erstellte eine DateTime mit "yyyy-MM-dd" und die DateTime, die von dem Objekt benötigt wurde, das ich für das Mapping von "HH-mm-ss" benötigte. Durch das Anhängen von "00-00" wurde das Problem behoben (das gesamte Element war deswegen null).

1
shocks

Ich hatte das gleiche Problem. 

In meinem Fall lag das Problem in der public int? CreditLimitBasedOn { get; set; }-Eigenschaft, die ich hatte. 

mein JSON hatte den Wert "CreditLimitBasedOn":true, wenn es eine ganze Zahl enthalten sollte. Diese Eigenschaft verhinderte, dass das gesamte Objekt auf meiner API-Methode deserialisiert wurde.

1

Vielleicht ist es für jemanden hilfreich: Überprüfen Sie die Zugriffsmodifizierer für die Eigenschaften Ihrer DTO/Model-Klasse. Diese sollten public sein. In meinem Fall wurden während des Refactorings Domänenobjekteinbauten wie folgt in DTO verschoben:

// 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 wird zwar fein an den Client übergeben, aber wenn das Objekt zurück an den Server übergeben werden soll, hatte es nur leere Felder (Nullwert/Standardwert). Durch das Entfernen von "intern" werden die Dinge in der richtigen Reihenfolge angeordnet, sodass der Deserialisierungsmechanismus die Eigenschaften des Objekts schreiben kann.

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

Überprüfen Sie, ob das Attribut JsonProperty für die Felder festgelegt ist, die als Null angegeben werden. Möglicherweise werden sie anderen json-Eigenschaftsnamen zugeordnet.

1

Ich habe HttpRequestMessage verwendet und mein Problem wurde nach so viel Recherche gelöst

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

Ich habe dieses Problem so oft getroffen, aber eigentlich ist es ziemlich einfach, die Ursache zu finden.

Hier ist das heutige Beispiel. Ich habe meinen POST - Dienst mit einem AccountRequest-Objekt aufgerufen, aber wenn ich am Anfang dieser Funktion einen Haltepunkt setze, war der Parameterwert immer null. Aber warum?!

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

    //  ... other code ... 
}

Um das Problem zu identifizieren, ändern Sie den Parametertyp in string, fügen Sie eine Zeile hinzu, um JSON.Net zu erhalten, um das Objekt in den erwarteten Typ zu deserialisieren, und setzen Sie einen Haltepunkt in diese Zeile:

[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 ...
}

Wenn Sie nun dies versuchen, wenn der Parameter noch leer ist oder null, rufen Sie den Dienst einfach nicht richtig auf.

Wenn jedoch die Zeichenfolge einen Wert enthält, sollte die DeserializeObject Sie auf die Ursache des Problems hinweisen und die Zeichenfolge auch nicht in das gewünschte Format konvertieren. Aber mit den Rohdaten (string), die es zu deserialisieren versucht, sollten Sie jetzt in der Lage sein, festzustellen, was mit Ihrem Parameterwert nicht stimmt.

(In meinem Fall haben wir den Dienst mit einem AccountRequest-Objekt aufgerufen, das versehentlich zweimal serialisiert wurde []!)

0
Mike Gledhill

Dieses Problem hatte ich in meiner .NET Framework-Web-API, da mein Modell in einem .NET-Standardprojekt vorhanden war, das auf eine andere Version von Datenanmerkungen verwies.

Durch das Hinzufügen der ReadAsAsync-Zeile unten wurde die Ursache für mich hervorgehoben:

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

Wenn Web API 2 aufgrund nicht übereinstimmender Datentypen zu einem Deserialisierungsproblem geführt hat, können Sie durch Untersuchen des Inhaltsstreams feststellen, wo es fehlgeschlagen ist. Es wird so lange gelesen, bis ein Fehler auftritt. Wenn Sie den Inhalt als Zeichenfolge lesen, sollten Sie die hintere Hälfte der von Ihnen geposteten Daten haben:

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

Korrigieren Sie diesen Parameter, und es sollte das nächste Mal weitergehen (oder wenn Sie Glück haben!) ...

0
Jereme

Anscheinend kann es viele verschiedene Ursachen für dieses Problem geben ...

Ich fand, dass das Hinzufügen eines OnDeserialized-Callbacks zur Modellklasse dazu führte, dass der Parameter immer null war. Genaue Ursache unbekannt.

using System.Runtime.Serialization;

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