it-roy-ru.com

веб-апи POST объект body всегда нулевой

Я все еще изучаю веб-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.

53
InnovativeDan

Странный атрибут FromBody в том смысле, что входные значения POST должны быть в определенном формате, чтобы параметр был ненулевым, когда он не является примитивным типом. (студент здесь) 

  1. Попробуйте ваш запрос с {"name":"John Doe", "age":18, "country":"United States of America"} в качестве json. 
  2. Удалите атрибут [FromBody] и попробуйте решение. Это должно работать для не примитивных типов. (ученик)
  3. С атрибутом [FromBody] другой вариант - отправлять значения в формате =Value, а не в формате key=value. Это будет означать, что значение вашего ключа student должно быть пустой строкой ... 

Существуют также другие варианты написания пользовательского связывателя модели для класса ученика и присвоения параметра вашему настраиваемому связывателю.

39
Raja Nadar

Я искал решение моей проблемы уже несколько минут, поэтому поделюсь своим решением. 

Ваша модель должна иметь пустой конструктор/конструктор по умолчанию, иначе модель не может быть создана, очевидно ... Будьте осторожны при рефакторинге. ;)

43
chrjs

Я провожу несколько часов с этой проблемой ... :( Геттеры и сеттеры ТРЕБУЮТСЯ в объявлении объекта параметров 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; }
}
22
Milan Švec

Это немного старый вопрос, и мой ответ опустится до последнего места, но я бы хотел поделиться своим опытом.

Пробовал каждое предложение, но все еще с тем же «нулевым» значением в PUT [FromBody].

Наконец, я обнаружил, что все дело в формате Date, когда JSON сериализует свойство EndDate моего Angular Object.

Ошибка не выдана, только что получен пустой объект FromBody ....

13
Josep Alacid

Если любое из значений объекта JSON запроса не соответствует типу, ожидаемому службой, тогда аргумент [FromBody] будет null.

Например, если свойство age в json имело значение float:

«Возраст»: 18,0

но служба API ожидает, что это int

"Возраст": 18

тогда student будет null. (Никаких сообщений об ошибках не будет отправлено в ответе, если нет проверки нулевой ссылки).

12
Ray Vega

Если вы используете Почтальон, убедитесь, что:

  • Вы установили заголовок «Content-Type» на «application/json»
  • Вы отправляете тело как "сырое"
  • Вам не нужно нигде указывать имя параметра, если вы используете [FromBody]

Я тупо пытался отправить свой JSON как данные формы, да ...

7
John M

TL; DR: не используйте [FromBody], но сверните свою собственную версию с лучшей обработкой ошибок. Причины приведены ниже.

Другие ответы описывают множество возможных причин этой проблемы. Однако основная причина в том, что [FromBody] просто ужасно обрабатывает ошибки, что делает его практически бесполезным в рабочем коде.

Например, одна из наиболее типичных причин того, что параметр является null, заключается в том, что тело запроса имеет неверный синтаксис (например, неверный JSON). В этом случае разумный API вернет 400 BAD REQUEST, а разумная веб-среда сделает это автоматически. Однако ASP.NET Web API не является разумным в этом отношении. Он просто устанавливает для параметра значение null, а затем обработчику запроса требуется «ручной» код, чтобы проверить, является ли параметр null.

Поэтому многие из приведенных здесь ответов являются неполными в отношении обработки ошибок, и глючный или злонамеренный клиент может вызвать неожиданное поведение на стороне сервера, отправив неверный запрос, который (в лучшем случае) выбросит NullReferenceException куда-нибудь и вернет неверный статус 500 INTERNAL SERVER ERROR или, что еще хуже, что-то неожиданное, сбой или выявление уязвимости безопасности.

Правильным решением было бы написать собственный атрибут «[FromBody]», который выполняет надлежащую обработку ошибок и возвращает надлежащие коды состояния, в идеале с некоторой диагностической информацией, чтобы помочь разработчикам клиента.

Решение, которое может помочь (еще не проверено), состоит в том, чтобы сделать необходимые параметры следующими: https://stackoverflow.com/a/19322688/2279059

Следующее неуклюжее решение также работает:

// 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: https://stackoverflow.com/a/38515689/2279059

Правка: Похоже, что ModelState.IsValid, как и следовало ожидать, не установлен на false, если используется JsonProperty с Required = Required.Always и параметр отсутствует. Так что это тоже бесполезно.

Однако, на мой взгляд, любое решение, требующее написания дополнительного кода в обработчике запросов each, неприемлемо. В таком языке, как .NET, с мощными возможностями сериализации и в среде, такой как ASP.NET Web API, проверка запросов должна быть автоматической и встроенной, и это полностью выполнимо, даже если Microsoft не предоставляет необходимых встроенных инструменты.

5
Florian Winter

Я также пытался использовать [FromBody], однако я пытался заполнить строковую переменную, потому что ввод будет изменяться, и мне просто нужно передать его бэкэнд-сервису, но это всегда было нулевым

Post([FromBody]string Input]) 

Поэтому я изменил сигнатуру метода для использования динамического класса, а затем преобразовал его в строку

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

Это хорошо работает.

3
Mike

Просто чтобы добавить мою историю в эту тему . Моя модель:

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
1
Dariusz

Может быть полезно добавить 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();

(возможно оберните это в # если DEBUG)

1
Phuo

В моем случае проблема была в объекте DateTime, который я отправлял. Я создал DateTime с помощью «yyyy-MM-dd», а DateTime, который требовался для объекта, который я отображал, также нуждался в «HH-mm-ss». Таким образом, добавление «00-00» решило проблему (из-за этого полный элемент был нулевым).

1
shocks

У меня такая же проблема. 

В моем случае проблема была в свойстве public int? CreditLimitBasedOn { get; set; }

мой JSON имел значение "CreditLimitBasedOn":true, когда он должен содержать целое число. Это свойство предотвращало десериализацию всего объекта в моем методе API.

1
Romesh D. Niriella

Может быть, для кого-то это будет полезно: проверьте модификаторы доступа для свойств вашего класса DTO/Model, они должны быть public . В моем случае во время рефакторинга внутренние объекты объекта были перемещены в 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/значение по умолчанию). Удаление «внутренних» приводит в порядок вещи, позволяя механизму десериализации записывать свойства объекта.

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

Проверьте, установлен ли атрибут JsonProperty для полей, которые имеют значение null - возможно, они сопоставлены с различными именами свойств json.

1
Bashir Magomedov

Я использовал HttpRequestMessage, и моя проблема была решена после стольких исследований

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

Я сталкивался с этой проблемой очень много раз, но на самом деле довольно просто отследить причину.

Вот сегодняшний пример. Я вызывал свой сервис POST с объектом 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, чтобы десериализовать объект в ожидаемый вами тип, и поместите точку останова в эту строку:

[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, который был случайно сериализован дважды!)

0
Mike Gledhill

У меня была эта проблема в моем .NET Framework Web API, потому что моя модель существовала в проекте .NET Standard, который ссылался на другую версию аннотаций данных.

Добавление строки ReadAsAsync ниже выделило причину для меня:

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

Если это связано с тем, что в Web API 2 возникла проблема десериализации из-за несоответствия типов данных, можно выяснить, где произошел сбой, проверив поток контента. Он будет читать до тех пор, пока не обнаружит ошибку, поэтому, если вы читаете содержимое в виде строки, у вас должна быть задняя половина отправленных вами данных:

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

Исправьте этот параметр, и в следующий раз он должен пройти дальше (или удастся, если вам повезет!) ...

0
Jereme

После трех дней поиска и ни одно из вышеперечисленных решений не помогло мне, я нашел другой подход к этой проблеме в этой ссылке: HttpRequestMessage

Я использовал одно из решений на этом сайте 

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

Кажется, может быть много разных причин этой проблемы ...

Я обнаружил, что добавление обратного вызова OnDeserialized к классу модели приводило к тому, что параметр всегда был null. Точная причина неизвестна.

using System.Runtime.Serialization;

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