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
.
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)
{"name":"John Doe", "age":18, "country":"United States of America"}
als json. [FromBody]
-Attribut, und versuchen Sie die Lösung. Es sollte für nicht primitive Typen funktionieren. (Student)[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.
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. ;)
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; }
}
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.
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 student
null
sein. (In der Antwort werden keine Fehlernachrichten gesendet, es sei denn, es wird keine Überprüfung auf Null durchgeführt.).
Wenn Sie Postman verwenden, stellen Sie Folgendes sicher:
Ich habe dumm versucht, meine JSON als Formulardaten zu senden, duh ...
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.
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.
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;
}
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
Guid
-Wert übergeben Guid
in String
ändernEs 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)
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).
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.
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; }
}
Ü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.
Ich habe HttpRequestMessage verwendet und mein Problem wurde nach so viel Recherche gelöst
[HttpPost]
public HttpResponseMessage PostProcedure(HttpRequestMessage req){
...
}
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 []!)
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>();
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!) ...
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() {...}