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 должны быть в определенном формате, чтобы параметр был ненулевым, когда он не является примитивным типом. (студент здесь)

  1. Попробуйте запрос с {"name":"John Doe", "age":18, "country":"United States of America"} в качестве json.
  2. Удалите [FromBody] и попробуйте решение. Это должно работать для не примитивных типов. (ученик)
  3. При использовании [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

...