Web-api POST body object всегда null
Я все еще изучаю веб-API, поэтому простите меня, если мой вопрос звучит глупо.
У меня это в моем 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);
}
Чтобы проверить, работает ли это, я использую расширение Google Chrome "Почтальон" для создания запроса HTTP POST для его проверки.
Это мой необработанный запрос POST:
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"
должен быть объектом, но когда я отлаживаю приложение, API получает объект-ученик, но содержимое всегда NULL
.
Ответы
Ответ 1
FromBody - странный атрибут в том, что входные значения POST должны быть в определенном формате, чтобы параметр был ненулевым, когда он не является примитивным типом. (студент здесь)
- Попробуйте запрос с
{"name":"John Doe", "age":18, "country":"United States of America"}
в качестве json. - Удалите
[FromBody]
и попробуйте решение. Это должно работать для не примитивных типов. (ученик) - При использовании
[FromBody]
другой вариант заключается в отправке значений в формате =Value
, а не в формате key=value
. Это будет означать, что ваше ключевое значение student
должно быть пустой строкой...
Существуют также другие варианты написания пользовательского связывателя модели для класса ученика и присвоения параметра вашему настраиваемому связывателю.
Ответ 2
Я искал решение своей проблемы в течение нескольких минут, поэтому я поделюсь своим решением.
В вашей модели должен быть пустой/стандартный конструктор, иначе модель не может быть создана, очевидно.
Будьте осторожны при рефакторинге.;)
Ответ 3
Я провожу несколько часов с этой проблемой...:( Getters и seters НЕОБХОДИМО в объявлении объекта параметров POST. Я не рекомендую использовать простые объекты данных (string, int,...), поскольку они требуют специального формата запроса.
[HttpPost]
public HttpResponseMessage PostProcedure(EdiconLogFilter filter){
...
}
Не работает, когда:
public class EdiconLogFilter
{
public string fClientName;
public string fUserName;
public string fMinutes;
public string fLogDate;
}
Работает нормально, когда:
public class EdiconLogFilter
{
public string fClientName { get; set; }
public string fUserName { get; set; }
public string fMinutes { get; set; }
public string fLogDate { get; set; }
}
Ответ 4
Это немного старый, и мой ответ опустится до последнего места, но даже я хотел бы поделиться своим опытом.
Пробовал каждое предложение, но все еще имело такое же "нулевое" значение в PUT [FromBody].
Наконец-то выяснилось, что речь идет о формате Date, а JSON - сериализации свойства EndDate моего объекта Angular.
Ошибка не была выбрана, просто получен пустой объект FromBody....
Ответ 5
Если любое из значений объекта запроса JSON не является тем же типом, что и ожидалось службой, то аргумент [FromBody]
будет null
.
Например, если свойство age в json имеет значение float
:
"возраст": 18,0
но служба API ожидает, что она будет int
"возраст": 18
тогда student
будет null
. (В ответе не будут отправляться сообщения об ошибках, если нет проверки нулевой ссылки).
Ответ 6
Если вы используете Postman, убедитесь, что:
- Вы установили заголовок "Content-Type" в "application/json"
- Вы отправляете тело как "сырое"
- Вам не нужно указывать имя параметра в любом месте, если вы используете [FromBody]
Я глупо пытался отправить свой JSON в виде данных формы, duh...
Ответ 7
TL; DR: не используйте [FromBody], но сверните свою собственную версию с лучшей обработкой ошибок. Причины приведены ниже.
Другие ответы описывают множество возможных причин этой проблемы. Однако [FromBody]
причина в том, что [FromBody]
просто ужасно обрабатывает ошибки, что делает его практически бесполезным в [FromBody]
коде.
Например, одна из наиболее типичных причин того, что параметр имеет значение null
заключается в том, что тело запроса имеет недопустимый синтаксис (например, недопустимый JSON). В этом случае разумный API вернет 400 BAD REQUEST
, а разумная веб-среда сделает это автоматически. Однако ASP.NET Web API не является разумным в этом отношении. Он просто устанавливает для параметра значение null
, а обработчику запроса затем требуется "ручной" код, чтобы проверить, является ли параметр null
.
Поэтому многие ответы, приведенные здесь, являются неполными в отношении обработки ошибок, и глючный или злонамеренный клиент может вызвать неожиданное поведение на стороне сервера, отправив неверный запрос, который (в лучшем случае) NullReferenceException
куда-то NullReferenceException
и возвращает неверный статус 500 INTERNAL SERVER ERROR
или, что еще хуже, сделать что-то неожиданное, аварийно завершить работу или раскрыть уязвимость системы безопасности
Правильным решением было бы написать собственный [FromBody]
" [FromBody]
", который правильно обрабатывает ошибки и возвращает правильные коды состояния, в идеале с некоторой диагностической информацией, чтобы помочь разработчикам клиента.
Решение, которое может помочь (еще не проверено), состоит в том, чтобы сделать необходимые параметры следующими: fooobar.com/questions/176023/...
Следующее неуклюжее решение также работает:
// 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 ...
}
Это делает (надеюсь) правильную обработку ошибок, но менее декларативно. Если, например, вы используете Swagger для документирования своего API, он не будет знать тип параметра, а это значит, что вам нужно найти какой-то ручной обходной путь для документирования ваших параметров. Это просто для иллюстрации того, что [FromBody]
должен делать.
РЕДАКТИРОВАТЬ: Менее неуклюжий решение состоит в том, чтобы проверить ModelState
: fooobar.com/questions/2331409/...
РЕДАКТИРОВАТЬ: Похоже, что ModelState.IsValid
, как и следовало ожидать, не установлен в false
если используется JsonProperty
с Required = Required.Always
и параметр отсутствует. Так что это тоже бесполезно.
Однако, на мой взгляд, любое решение, требующее написания дополнительного кода в каждом обработчике запросов, неприемлемо. В таком языке, как .NET, с мощными возможностями сериализации и в среде, такой как ASP.NET Web API, проверка запросов должна быть автоматической и встроенной, и это полностью выполнимо, даже если Microsoft не предоставляет необходимых встроенных инструменты.
Ответ 8
Я также пытался использовать [FromBody], однако я пытался заполнить строковую переменную, потому что вход будет меняться, и мне просто нужно передать его вместе с бэкэнд-службой, но это всегда было нулевым
Post([FromBody]string Input])
Итак, я изменил подпись метода для использования динамического класса, а затем преобразовал его в строку
Post(dynamic DynamicClass)
{
string Input = JsonConvert.SerializeObject(DynamicClass);
Это хорошо работает.
Ответ 9
Может быть полезно добавить TRACING в сериализатор json, чтобы вы могли видеть, что происходит, когда что-то идет не так.
Определите реализацию ITraceWriter, чтобы показать их вывод отладки, например:
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);
}
}
Затем в вашем WebApiConfig выполните:
config.Formatters.JsonFormatter.SerializerSettings.TraceWriter = new TraceWriter();
(возможно, оберните его в #if DEBUG)
Ответ 10
В моем случае проблема была в объекте DateTime
я отправлял. Я создал DateTime
с помощью "yyyy-MM-dd", а DateTime
который требовался для объекта, который я отображал, также требовал "HH-mm-ss". Таким образом, добавление "00-00" решило проблему (из-за этого полный элемент был нулевым).
Ответ 11
После трех дней поиска и ни одно из вышеперечисленных решений не помогло мне, я нашел другой подход к этой проблеме в этой ссылке: HttpRequestMessage
Я использовал одно из решений на этом сайте
[HttpPost]
public async System.Threading.Tasks.Task<string> Post(HttpRequestMessage request)
{
string body = await request.Content.ReadAsStringAsync();
return body;
}
Ответ 12
Это еще одна проблема, связанная с недопустимыми значениями свойств в запросе Angular Typescript.
Это было связано с преобразованием числа Typescript в int (Int32) в С#. Я использовал Ticks (UTC миллисекунды), который больше, чем подписанный диапазон Int32 (int в С#). Изменил модель С# с int на long и все работало нормально.
Ответ 13
У меня была та же проблема.
В моем случае проблема заключалась в свойстве public int? CreditLimitBasedOn { get; set; }
, которое у меня было.
у моего JSON было значение "CreditLimitBasedOn":true
, когда оно должно содержать целое число. Это свойство предотвратило десериализацию всего объекта по моему методу api.
Ответ 14
Возможно, кому-то это будет полезно: проверьте модификаторы доступа для свойств класса DTO/Model, они должны быть общедоступными. В моем случае во время реорганизации внутренние объекты домена были перемещены в DTO следующим образом:
// 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 точно передается клиенту, но когда приходит время вернуть объект обратно на сервер, он имеет только пустые поля (значение null/default). Удаление "внутренних" приводит порядок вещей, позволяя механизму десериализации для записи свойств объекта.
public class MyDomainObjectDto {
public Name { get; set; }
public string Info { get; set; }
}
Ответ 15
Просто чтобы добавить мою историю в эту ветку. Моя модель:
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()
{
}
}
Вышеуказанный объект не может быть сериализован в моем контроллере API и всегда будет возвращать ноль. Проблема была с идентификатором типа Guid: каждый раз, когда я передавал пустую строку в качестве идентификатора (наивно, что он будет автоматически преобразован в Guid.Empty
) из моего интерфейса, я получал нулевой объект в качестве параметра [FromBody]
.
Решение было либо
- передать действительное значение
Guid
- или измените
Guid
на String
Ответ 16
Проверьте, установлен ли атрибут JsonProperty
для полей, которые имеют значение null - возможно, они сопоставлены с различными именами свойств json.
Ответ 17
Я сталкивался с этой проблемой много раз, но на самом деле довольно просто найти причину.
Вот сегодня пример. Я вызывал свой POST-сервис с объектом AccountRequest
, но когда я ставил AccountRequest
останова в начале этой функции, значение параметра всегда было null
. Но почему?!
[ProducesResponseType(typeof(DocumentInfo[]), 201)]
[HttpPost]
public async Task<IActionResult> Post([FromBody] AccountRequest accountRequest)
{
// At this point... accountRequest is null... but why ?!
// ... other code ...
}
Чтобы определить проблему, измените тип параметра на string
, добавьте строку, чтобы получить JSON.Net
для десериализации объекта в JSON.Net
вами тип, и поместите JSON.Net
останова в эту строку:
[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 ...
}
Теперь, когда вы попробуете это, если параметр все еще пустой или null
, вы просто не вызываете сервис должным образом.
Однако, если строка действительно содержит значение, тогда DeserializeObject
должен указать вам причину проблемы, а также не сможет преобразовать вашу строку в желаемый формат. Но с необработанными (string
) данными, которые он пытается десериализовать, вы теперь сможете увидеть, что не так с вашим значением параметра.
(В моем случае мы AccountRequest
сервис с объектом AccountRequest
который был случайно сериализован дважды!)
Ответ 18
Я использовал HttpRequestMessage, и моя проблема была решена после стольких исследований
[HttpPost]
public HttpResponseMessage PostProcedure(HttpRequestMessage req){
...
}
Ответ 19
Похоже, может быть много разных причин этой проблемы...
Я обнаружил, что добавление обратного вызова OnDeserialized
к классу модели заставило параметр всегда быть null
. Точная причина неизвестна.
using System.Runtime.Serialization;
// Validate request
[OnDeserialized] // TODO: Causes parameter to be null
public void DoAdditionalValidatation() {...}
Ответ 20
Если это связано с тем, что в Web API 2 возникла проблема десериализации из-за несоответствия типов данных, можно выяснить, где произошла ошибка, проверив поток контента. Он будет читать до тех пор, пока не обнаружит ошибку, поэтому, если вы читаете содержимое в виде строки, у вас должна быть задняя половина отправленных вами данных:
string json = await Request.Content.ReadAsStringAsync();
Исправьте этот параметр, и в следующий раз он должен пройти дальше (или удастся, если вам повезет!)...
Ответ 21
У меня была эта проблема в моем.NET Framework Web API, потому что моя модель существовала в проекте.NET Standard, который ссылался на другую версию аннотаций данных.
Добавление строки ReadAsAsync ниже выделило причину для меня:
public async Task<HttpResponseMessage> Register(RegistrationDetails registrationDetails)
{
var regDetails = await Request.Content.ReadAsAsync<RegistrationDetails>();
Ответ 22
Я использовал Почтальон, чтобы поразить сервер. После нескольких часов исследований и испытаний я нашел свою ошибку.
Выберите "JSON (application/json)" при вводе "сырого" тела в Postman. прикрепленный скриншот
Ответ 23
В моем случае, используя почтальон, я отправлял DateTime с недопустимыми разделителями (%), поэтому синтаксический анализ не удался.
Убедитесь, что вы передаете действительные параметры в конструктор вашего класса.
Ответ 24
Ничто из вышеперечисленного не было моим решением: в моем случае проблема в том, что [ApiController] не был добавлен в контроллер, поэтому он дает значение Null
[Produces("application/json")]
[Route("api/[controller]")]
[ApiController] // This was my problem, make sure that it is there!
public class OrderController : Controller
...