web-api POST正文对象始终为null


77

我仍在学习Web 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的扩展程序“ Postman”来构造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会接收到该student对象,但内容始终是null


3
只是向在以后发现类似问题的任何人(作为我刚才做过的一个便笺):Web API应该返回包含异常的JSON(就像您在代码中捕获了异常一样),该异常可用于诊断问题。似乎很明显,但是我怀疑我不是唯一不想检查响应并认为它只是标准HTTP响应代码的人!
乔恩·斯托利

Answers:


59

FromBody是一个奇怪的属性,因为输入POST值不是原始类型时,其输入POST值必须采用特定格式才能使参数为非null。(这里的学生)

  1. 尝试使用您的请求{"name":"John Doe", "age":18, "country":"United States of America"}作为json。
  2. 删除该[FromBody]属性,然后尝试解决方案。它应该适用于非原始类型。(学生)
  3. 使用该[FromBody]属性,另一个选择是以=Value格式而不是key=valueformat发送值。这意味着您的键值student应为空字符串...

还有其他选项可以为学生班级编写自定义模型联编程序,并使用自定义联编程序为参数赋予属性。


3
删除了[FromBody],仍然无法使用。学生对象的内容仍然为null。(例如,姓名= null,国家/地区= null,年龄= null)
InnovativeDan

有效!没想到问题出在我的JSON格式上。谢啦!标记为答案。
InnovativeDan

1
#1为我工作。谢谢:)我只需要从JSON中删除“键”并发送“值”
IsolatedStorage

12
对于其他读者..最初的问题是OP发送包含学生属性的JSON对象,而不是像webapi期望的那样仅发送学生对象。
肖恩·威尔逊

3
这是一个较晚的评论,但是您的解决方案帮助我解决了过去两个小时以来我一直在努力解决的问题。干杯:)
iSahilSharma

59

我几分钟以来一直在寻找解决方案,所以我将分享解决方案。

当模型中有自定义构造函数时,模型还需要具有空/默认构造函数。显然,否则无法创建模型。重构时要小心。


1
这是必不可少的信息,应该添加到接受的答案中。
Youkko

1
这根本不准确。类(模型是类)具有默认的构造函数,即使未指定,也可以肯定创建它
Andrei Dragotoniu

我同意@AndreiDragotoniu。这似乎不是事实。我遇到了这个问题,而我的问题是因为我拥有只读属性。向每个添加setter之后,它可以正常工作(即使只指定一个非空的构造函数)。
Broots Waymb,

2
@AndreiDragotoniu我认为,当您创建需要参数的构造函数时,就会出现问题。如果这样做,它将覆盖默认构造函数。在这种情况下,您还需要创建一个附加的空构造函数。
LairdPleng '20

1
@LairdPleng感谢您的澄清。我相应地编辑了答案。
chrjs

38

我花了几个小时来解决这个问题... :( POST参数对象声明中需要Getter和setter。我不建议使用简单的数据对象(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; }
}

1
噢,我真不敢相信这实际上是我的问题。
克里斯(Chris

带有getter和setter的Ne​​eding Properties解决了我的GET请求的问题,这是一个令人讨厌的问题
Mart10,18年

谢谢!如果我没有阅读您的答案,我也不会打扰仔细检查,并且会注意到DTO的set属性上的access修饰符确实存在,internal并且这就是问题所在。
西蒙·摩根

哇,这也是我的解决方案。人民,当心私人二传手!取代“私人套装”;设置;” 解决了我的问题。
FBaez51 '19

19

如果请求的JSON对象的任何值与服务期望的类型都不相同,则[FromBody]参数将为null

例如,如果json中的age属性具有float值:

年龄:18.0

但API服务希望它是一个 int

“年龄”:18

然后studentnull。(除非没有空引用检查,否则不会在响应中发送任何错误消息)。


非常感谢你!我们试图将一个复杂的对象绑定到一个字符串(上帝知道为什么)。修复后,它起作用了。这个问题使我发疯。一个愚蠢的决定,默默地失败并返回null ……
卢卡斯·布拉

13

这是一个有点老了,我的答案将降到最后一位,但即使如此,我也想分享我的经验。

尝试了每个建议,但在PUT [FromBody]中仍具有相同的“空”值。

最终发现,这与JSON序列化我的Angular Object的EndDate属性有关的日期格式有关。

没有引发任何错误,只是收到了一个空的FromBody对象。


3
我一直在传递各种奇怪的数据给它,希望它在映射中很聪明,但是没有……它死了,除了null之外什么都给你。我再次尝试使用行为良好的数据,然后按顺序整理一下。您的答案是鼓励我这样做的答案!
MGDavies

2
这个答案非常有用,因为日期格式也是我遇到错误的原因。
伊莱贾·洛夫格伦

当参数在POST请求的主体内作为JSON传递时,请避免在参数前使用[FromBody]。另外,请确保JSON中的顶级名称与参数的变量名称匹配。
justdan23 '18

谢谢,你是真正的上帝。
冲野浩文

10

如果使用Postman,请确保:

  • 您已将“ Content-Type”标头设置为“ application / json”
  • 您将身体发送为“原始”
  • 如果使用[FromBody],则无需在任何地方指定参数名称

我愚蠢地试图将JSON作为表单数据发送,嗯...


对我来说也是一样。感谢您提供详细的答案。
goku_da_master

那你如何塑造自己的身体呢?我的意思是,如果我只在正文中声明“ = This is my value”(声明为JSON),这只会告诉我JSON是错误的。举个小例子?编辑:没关系,只是找到了解决方案。在我的情况下,“ Content-Type”必须为“ application / x-www-form-urlencoded”,而我的正文为Text类型的“ raw”。
杰克斯

如果不使用[FromBody],则只要JSON中的顶级名称与参数的变量名称匹配,它就应该解析JSON。
justdan23 '18年

这正是我的问题!非常感谢。
jay-danger

8

TL; DR:不要使用[FromBody],但要滚动使用自己的版本,并具有更好的错误处理能力。原因如下。

其他答案描述了此问题的许多可能原因。但是,根本原因是[FromBody]简单的错误处理非常糟糕,这使其在生产代码中几乎毫无用处。

例如,参数成为的最典型原因之一null是请求正文具有无效的语法(例如,无效的JSON)。在这种情况下,将返回一个合理的API 400 BAD REQUEST,并且一个合理的Web框架将自动执行此操作。但是,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]应。

编辑:一个不太笨拙的解决方案是检查ModelStatehttps : //stackoverflow.com/a/38515689/2279059

编辑:似乎ModelState.IsValid没有像人们期望的那样设置为,false如果使用JsonPropertywithRequired = Required.Always且缺少参数。所以这也是没有用的。

但是,我认为,任何需要在每个请求处理程序中编写其他代码的解决方案都是不可接受的。在具有强大序列化功能的.NET语言和ASP.NET Web API这样的框架中,请求验证应该是自动的并且是内置的,即使Microsoft没有提供必要的内置功能,也完全可以执行工具。


是的,添加上面提到的代码正是我想要的!添加代码以检查ModelState:stackoverflow.com/a/38515689/2279059
Rob Bramhall,

@RobBramhall如果使用DataContract而不是Newtonsoft.Json属性,则使用ModelState可能很好。
Florian

4

我也试图使用[FromBody],但是,我试图填充字符串变量,因为输入将发生变化,我只需要将其传递给后端服务即可,但这始终为null

Post([FromBody]string Input]) 

所以我将方法签名更改为使用动态类,然后将其转换为字符串

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

这很好。


4

将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中)


3

经过三天的搜索,以上解决方案均不适用于我,我在此链接中找到了解决此问题的另一种方法: HttpRequestMessage

我在这个网站上使用了一种解决方案

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

好主啊 这是在这里猛烈进行了2天。非常感谢你!
Yordan Yanakiev '19

为什么只有HttpRequestMessage参数起作用,我的控制器还有另一种使用([FromBody] ICollection <type> param)的方法,它却完美地发挥了作用,就像我所有其他控制器一样。当几天前我第一次实现该问题时,开始给我带来该问题的操作方法就可以正常工作,不同之处在于引用.net框架项目的.NET标准正在调用此Web API方法,所以我想知道这是否是如果.net标准引用.net框架最终导致的“未知领域”?我只是不想重新做这个项目。
尼诺斯

2

就我而言,问题是DateTime我发送的对象。我DateTime用“ yyyy-MM-dd”创建了一个,并且DateTime该对象也是我映射到所需的“ HH-mm-ss”所必需的。因此,附加“ 00-00”可以解决问题(因此,整个项目为null)。


2

这是与Angular Typescript请求中的无效属性值有关的另一个问题。

这与C#中的Typescript编号到int(Int32)之间的转换有关。我使用的是Ticks(UTC毫秒),它大于有符号的Int32范围(C#中的int)。将C#模型从int更改为long,一切正常。


1

我有同样的问题。

就我而言,问题出在public int? CreditLimitBasedOn { get; set; }我的财产上。

我的JSON具有"CreditLimitBasedOn":true应包含整数的值。此属性可防止在我的api方法上反序列化整个对象。


1

也许对某人有用:检查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; }
}

是我的问题,已经宣布会员internal为我的坏人,并在两天后解决了这个问题
。– Irfan

1

检查是否JsonProperty在作为null的字段上设置了属性-可能是它们映射到了不同的json属性名称。


这也是我的问题。我也注意到,我可以用[JsonProperty(“myProperty的”)装饰我的财产或者我可以使用[DataMember]属性,让我的属性的值进行传播。
Dan Beaulieu

1

我已经多次遇到此问题,但实际上,找出原因很简单。

这是今天的例子。我当时用一个AccountRequest对象调用POST服务,但是当我在此函数的开头放置一个断点时,参数值始终为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意外序列化了两次的对象来调用服务!)



1

只是为了将我的历史记录添加到该线程中。我的模特:

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控制器中序列化,并且始终返回null。问题在于Guid类型的ID:每次我Guid.Empty从前端将空字符串作为ID传递(天真地将其自动转换为)时,都会收到null对象,[FromBody]

解决方案是

  • 通过有效 Guid价值
  • 或更改GuidString

1

在我的情况下,我使用邮递员发送带有无效分隔符(%)的DateTime,因此解析无声地失败了。确保将有效的参数传递给类构造函数。


1

以上都不是我的解决方案:就我而言,问题是未将[ApiController]添加到控制器中,因此它给出了Null值

[Produces("application/json")]
[Route("api/[controller]")]
[ApiController] // This was my problem, make sure that it is there!
public class OrderController : Controller

...


ApiController自身继承怎么样?
Zimano

检查您的json结构,可能包含无效的json,例如通过jsonformatter.curiousconcept.com
Adel

如果您是在回应我:我的意思是您的答案建议ApiController在类继承自Controller;的同时添加as作为类属性。我相信您可以通过ApiController直接继承来实现相同的结果,默认情况下,这是在VS中由WebAPI项目生成的Controller中完成的。
Zimano

1

我只是碰到这个而感到沮丧。我的设置:标头设置为Content-Type:application / JSON,并以JSON格式从正文传递信息,并在控制器上读取[FromBody]。

一切设置都很好,我希望它能正常工作,但是问题在于发送的JSON。由于这是一个复杂的结构,因此我定义为“抽象”的类之一没有得到初始化,因此未将值正确分配给模型。我删除了abstract关键字,它才起作用.. !!!

一个技巧,我可以弄清楚这一点的方法是将数据部分发送到控制器,并检查它是否为空...由于这是一个复杂的模型,因此我一次将一个模型追加到我的请求参数中。希望它能帮助遇到这个愚蠢问题的人。


0

似乎可能有许多不同的原因导致此问题...

我发现向OnDeserialized模型类添加回调会导致参数始终为null。确切原因未知。

using System.Runtime.Serialization;

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

0

我的.NET Framework Web API中存在此问题,因为我的模型存在于引用不同版本的数据批注的.NET Standard项目中。

在下面添加ReadAsAsync行突出显示了我的原因:

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

0

如果这是因为Web API 2由于数据类型不匹配而遇到反序列化问题,则可以通过检查内容流来找出失败的地方。它会一直读取直到遇到错误为止,因此,如果您以字符串形式读取内容,则应该拥有发布数据的后半部分:

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

修复该参数,下次可以使用该参数(如果幸运的话,也可以成功!)...


如果您的服务每秒收到1000个以上的请求,然后字符串为null,则Request.Content.ReadAsStringAsync将受到影响。我们需要处理这个。
Amol Shiledar '18

0

就我而言(.NET Core 3.0),我必须配置JSON序列化以使用AddNewtonsoftJson()解析camelCase属性:

services.AddMvc(options =>
{
    // (Irrelevant for the answer)
})
.AddNewtonsoftJson(options =>
{
    options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});

在“启动/依赖注入”设置中执行此操作。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.