Ich lerne immer noch Web-API, also entschuldigen Sie, wenn meine Frage dumm klingt.
Ich habe dies in meinem 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 Google Chrome-Erweiterung "Postman", um die HTTP-POST-Anforderung 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 wenn ich die Anwendung debugge, empfängt die API das student
Objekt, aber der Inhalt ist immer null
.
c#
rest
asp.net-web-api
InnovativeDan
quelle
quelle
Antworten:
FromBody ist insofern ein seltsames Attribut, als die eingegebenen POST-Werte in einem bestimmten Format vorliegen 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 probieren Sie die Lösung aus. Es sollte für nicht-primitive Typen funktionieren. (Schüler)[FromBody]
Attribut besteht die andere Option darin, die Werte im=Value
Format und nicht im Format zu sendenkey=value
. Dies würde bedeuten, dass Ihr Schlüsselwert vonstudent
eine leere Zeichenfolge sein sollte ...Es gibt auch andere Optionen, um einen benutzerdefinierten Modellordner für die Schülerklasse zu schreiben und den Parameter Ihrem benutzerdefinierten Ordner zuzuordnen.
quelle
Ich habe jetzt einige Minuten nach einer Lösung für mein Problem gesucht, also werde ich meine Lösung teilen.
Wenn Sie einen benutzerdefinierten Konstruktor in Ihrem Modell haben, muss Ihr Modell auch einen leeren / Standardkonstruktor haben. Andernfalls kann das Modell natürlich nicht erstellt werden. Seien Sie vorsichtig beim Refactoring.
quelle
Ich verbringe mehrere Stunden mit diesem Problem ... :( Getter und Setter sind in der POST-Parameter-Objektdeklaration ERFORDERLICH. Ich empfehle nicht, einfache Datenobjekte (Zeichenfolge, Int, ...) zu verwenden, 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; } }
quelle
internal
und dies das Problem verursachte.Wenn die Werte des JSON-Objekts der Anforderung nicht den vom Dienst erwarteten Typ haben, lautet das
[FromBody]
Argumentnull
.Zum Beispiel, wenn die Alterseigenschaft im json einen
float
Wert hatte:Der API-Dienst erwartet jedoch, dass es sich um eine handelt
int
dann
student
wird es seinnull
. (In der Antwort werden keine Fehlermeldungen gesendet, es sei denn, es wird keine Nullreferenzprüfung durchgeführt.)quelle
null
...Dies ist ein wenig 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 es um das Datumsformat ging, während JSON die EndDate-Eigenschaft meines Angular-Objekts serialisierte.
Es wurde kein Fehler ausgegeben, sondern nur ein leeres FromBody-Objekt empfangen.
quelle
Wenn Sie Postman verwenden, stellen Sie Folgendes sicher:
Ich habe dumm versucht, meinen JSON als Formulardaten zu senden, duh ...
quelle
TL; DR: Verwenden Sie nicht [FromBody], sondern rollen Sie Ihre eigene Version mit besserer Fehlerbehandlung. Gründe unten angegeben.
Andere Antworten beschreiben viele mögliche Ursachen für dieses Problem. Die Hauptursache ist jedoch, dass es
[FromBody]
einfach eine schreckliche Fehlerbehandlung gibt, die es im Produktionscode fast unbrauchbar macht.Einer der typischsten Gründe für den Parameter
null
ist beispielsweise, dass der Anforderungshauptteil eine ungültige Syntax aufweist (z. B. ungültiges JSON). In diesem Fall würde eine vernünftige API zurückkehren400 BAD REQUEST
, und ein vernünftiges Webframework würde dies automatisch tun. Die ASP.NET-Web-API ist in dieser Hinsicht jedoch nicht sinnvoll. Es setzt einfach den Parameter aufnull
und der Anforderungshandler benötigt dann "manuellen" Code, um zu überprüfen, ob der Parameter istnull
.Viele der hier gegebenen Antworten sind daher in Bezug auf die Fehlerbehandlung unvollständig, und ein fehlerhafter oder böswilliger Client kann auf der Serverseite unerwartetes Verhalten verursachen, indem er eine ungültige Anfrage sendet, die (im besten Fall) eine
NullReferenceException
irgendwo auslöst und eine falsche zurückgibt Status von500 INTERNAL SERVER ERROR
oder, schlimmer noch, etwas Unerwartetes tun oder abstürzen oder eine Sicherheitslücke aufdecken.Eine geeignete Lösung wäre, ein benutzerdefiniertes "
[FromBody]
" Attribut zu schreiben , das die ordnungsgemäße Fehlerbehandlung durchführt und die richtigen Statuscodes zurückgibt, idealerweise mit einigen Diagnoseinformationen, um Cliententwicklern zu helfen.Eine Lösung, die möglicherweise hilft (noch nicht getestet), besteht darin, die erforderlichen Parameter wie folgt 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 ordnungsgemäßen Fehlerbehandlung, ist jedoch weniger deklarativ. Wenn Sie beispielsweise Swagger zum Dokumentieren Ihrer API verwenden, ist der Parametertyp nicht bekannt. Dies bedeutet, dass Sie eine manuelle Problemumgehung finden müssen, um Ihre Parameter zu dokumentieren. Dies soll nur veranschaulichen, was
[FromBody]
zu tun ist.BEARBEITEN: Eine weniger umständliche Lösung ist zu überprüfen
ModelState
: https://stackoverflow.com/a/38515689/2279059BEARBEITEN: Es scheint, dass
ModelState.IsValid
es nicht wie erwartet auf gesetzt ist,false
wennJsonProperty
mit verwendet wirdRequired = Required.Always
und ein Parameter fehlt. Das ist also auch nutzlos.Meiner Meinung nach ist jedoch jede Lösung, die das Schreiben von zusätzlichem Code in jeden Anforderungshandler 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 vollständig machbar, obwohl Microsoft nicht die erforderlichen integrierten Funktionen bereitstellt Werkzeuge.
quelle
Ich habe auch versucht, [FromBody] zu verwenden, aber ich habe versucht, eine Zeichenfolgenvariable zu füllen, da sich die Eingabe ändert und ich sie nur an einen Backend-Dienst weitergeben muss, aber dies war immer null
Post([FromBody]string Input])
Also habe ich die Methodensignatur geändert, um eine dynamische Klasse zu verwenden, und diese dann in einen String konvertiert
Post(dynamic DynamicClass) { string Input = JsonConvert.SerializeObject(DynamicClass);
Das funktioniert gut.
quelle
Es kann hilfreich sein, dem json-Serializer TRACING hinzuzufügen, damit Sie sehen können, was los ist, wenn etwas schief geht.
Definieren Sie eine ITraceWriter-Implementierung, um ihre Debug-Ausgabe wie folgt 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 machen Sie in Ihrer WebApiConfig:
config.Formatters.JsonFormatter.SerializerSettings.TraceWriter = new TraceWriter();
(vielleicht in ein #if DEBUG einwickeln)
quelle
Nachdem ich drei Tage lang gesucht hatte und keine der oben genannten Lösungen für mich funktioniert hatte, fand ich in diesem Link einen anderen Ansatz für dieses Problem: 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; }
quelle
In meinem Fall war das Problem das
DateTime
Objekt, das ich gesendet habe. Ich habe einDateTime
mit "JJJJ-MM-TT" erstellt, und dasDateTime
, was für das Objekt erforderlich war, dem ich zugeordnet war, benötigte auch "HH- MM- SS". Das Anhängen von "00-00" löste das Problem (der vollständige Eintrag war aus diesem Grund null).quelle
Dies ist ein weiteres Problem im Zusammenhang mit ungültigen Eigenschaftswerten in einer Angular Typescript-Anforderung.
Dies hängt mit der Konvertierung zwischen einer Typoskriptnummer in ein int (Int32) in C # zusammen. Ich habe Ticks (UTC-Millisekunden) verwendet, die größer sind als der vorzeichenbehaftete Int32-Bereich (int in C #). Das C # -Modell wurde von int auf long geändert und alles hat gut funktioniert.
quelle
Ich hatte das gleiche Problem.
In meinem Fall lag das Problem in meinem
public int? CreditLimitBasedOn { get; set; }
Eigentum.Mein JSON hatte den Wert,
"CreditLimitBasedOn":true
wenn es eine ganze Zahl enthalten sollte. Diese Eigenschaft hat verhindert, dass das gesamte Objekt mit meiner API-Methode deserialisiert wird.quelle
Vielleicht ist es für jemanden hilfreich: Überprüfen Sie die Zugriffsmodifikatoren für die Eigenschaften Ihrer DTO / Modellklasse. Sie sollten öffentlich sein . In meinem Fall wurden Interna von Objektobjekten während des Refactorings 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 fein an den Client übergeben, aber wenn die Zeit gekommen ist, das Objekt an den Server zurückzugeben, hatte es nur leere Felder (Null / Standardwert). Durch das Entfernen von "intern" werden die Dinge in Ordnung gebracht, sodass der Deserialisierungsmechanismus die Eigenschaften des Objekts schreiben kann.
public class MyDomainObjectDto { public Name { get; set; } public string Info { get; set; } }
quelle
internal
für mein schlechtes erklärt und es nach zwei Tagen gelöst.Überprüfen Sie, ob
JsonProperty
für die Felder, die als null angegeben sind, ein Attribut festgelegt ist. Möglicherweise werden sie verschiedenen json-Eigenschaftsnamen zugeordnet.quelle
[DataMember]
Attribut verwenden konnte, um meine Eigenschaftenwerte zur Weitergabe zu bringen.Ich habe dieses Problem so oft getroffen, aber eigentlich ist es ziemlich einfach, die Ursache aufzuspüren.
Hier ist das heutige Beispiel. Ich habe meinen POST-Dienst mit einem
AccountRequest
Objekt aufgerufen, aber wenn ich am Anfang dieser Funktion einen Haltepunkt gesetzt habe, war der Parameterwert immernull
. 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, umJSON.Net
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 dies versuchen, wenn der Parameter noch leer ist oder
null
, rufen Sie den Dienst einfach nicht richtig auf.Wenn die Zeichenfolge jedoch einen Wert enthält,
DeserializeObject
sollte der Sie auf die Ursache des Problems hinweisen und die Zeichenfolge auch nicht in das gewünschte Format konvertieren. Mit den Rohdaten (string
), die deserialisiert werden sollen, sollten Sie jetzt sehen können, was mit Ihrem Parameterwert nicht stimmt.(In meinem Fall haben wir den Dienst mit einem
AccountRequest
Objekt angerufen, das versehentlich zweimal serialisiert wurde !)quelle
Ich habe HttpRequestMessage verwendet und mein Problem wurde nach so vielen Recherchen gelöst
[HttpPost] public HttpResponseMessage PostProcedure(HttpRequestMessage req){ ... }
quelle
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() { } }
Das obige Objekt konnte in meinem API-Controller nicht serialisiert werden und gab immer null zurück. Das Problem war mit der ID vom Typ Guid : Jedes Mal, wenn ich eine leere Zeichenfolge als ID (die naiv ist, in die sie automatisch konvertiert wird
Guid.Empty
) von meinem Frontend übergeben habe, habe ich ein Nullobjekt als[FromBody]
Parameter erhalten.Lösung war entweder zu
Guid
Wert übergebenGuid
zuString
quelle
In meinem Fall habe ich mit Postman eine DateTime mit ungültigen Trennzeichen (%) gesendet, sodass die Analyse unbeaufsichtigt fehlschlug. Stellen Sie sicher, dass Sie gültige Parameter an Ihren Klassenkonstruktor übergeben.
quelle
Keine der oben genannten Lösungen war meine Lösung: In meinem Fall besteht das Problem darin, dass [ApiController] nicht zum Controller hinzugefügt wurde, sodass der Wert Null angegeben wird
[Produces("application/json")] [Route("api/[controller]")] [ApiController] // This was my problem, make sure that it is there! public class OrderController : Controller
...
quelle
ApiController
selbst?ApiController
Attribut als Klasse hinzuzufügen , während die Klasse von erbtController
. Ich glaube, Sie können das gleiche Ergebnis erzielen, indem SieApiController
direkt von erben , was standardmäßig in den Controllern erfolgt, die von einem WebAPI-Projekt in VS generiert wurden.Ich bin gerade darauf gestoßen und war frustrierend. Mein Setup: Der Header wurde auf Content-Type: application / JSON gesetzt und übergab die Informationen aus dem Body im JSON-Format und las [FromBody] auf dem Controller.
Alles war gut eingerichtet und ich erwarte, dass es funktioniert, aber das Problem war, dass der JSON gesendet wurde. Da es sich um eine komplexe Struktur handelte, wurde eine meiner Klassen, die als "Zusammenfassung" definiert wurde, nicht initialisiert, und daher wurden die Werte dem Modell nicht ordnungsgemäß zugewiesen. Ich habe das abstrakte Schlüsselwort entfernt und es hat einfach funktioniert .. !!!
Ein Tipp, wie ich das herausfinden konnte, war, Daten in Teilen an meinen Controller zu senden und zu überprüfen, wann sie null werden ... da es sich um ein komplexes Modell handelte, habe ich jeweils ein Modell an meine Anforderungsparameter angehängt. Hoffe, es hilft jemandem, der auf dieses dumme Problem stößt.
quelle
Es scheint, dass es viele verschiedene Ursachen für dieses Problem geben kann ...
Ich fand heraus, dass das Hinzufügen eines
OnDeserialized
Rückrufs zur Modellklasse dazu führte, dass der Parameter immer warnull
. Genauer Grund unbekannt.using System.Runtime.Serialization; // Validate request [OnDeserialized] // TODO: Causes parameter to be null public void DoAdditionalValidatation() {...}
quelle
Ich hatte dieses Problem in meiner .NET Framework-Web-API, weil mein Modell in einem .NET Standard-Projekt vorhanden war, das auf eine andere Version von Datenanmerkungen verwies.
Durch Hinzufügen der folgenden ReadAsAsync-Zeile wurde die Ursache für mich hervorgehoben:
public async Task<HttpResponseMessage> Register(RegistrationDetails registrationDetails) { var regDetails = await Request.Content.ReadAsAsync<RegistrationDetails>();
quelle
Wenn dies darauf zurückzuführen ist, dass bei Web API 2 aufgrund nicht übereinstimmender Datentypen ein Deserialisierungsproblem aufgetreten ist, können Sie anhand des Inhaltsstroms herausfinden, wo dies fehlgeschlagen ist. Es wird gelesen, bis ein Fehler auftritt. Wenn Sie also 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 er sollte ihn beim nächsten Mal weiter verbessern (oder erfolgreich sein, wenn Sie Glück haben!) ...
quelle
In meinem Fall ( .NET Core 3.0 ) musste ich die JSON-Serialisierung konfigurieren, um die camelCase-Eigenschaften mithilfe von AddNewtonsoftJson () aufzulösen :
services.AddMvc(options => { // (Irrelevant for the answer) }) .AddNewtonsoftJson(options => { options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); });
Tun Sie dies in Ihrem Startup / Dependency Injection-Setup.
quelle
Nur noch eine Sache zu sehen ... mein Modell wurde als [serialisierbar] markiert und das verursachte den Fehler.
quelle