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

时间:2014-03-30 08:37:44

标签: c# rest asp.net-web-api

我还在学习网络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会收到学生对象,但内容始终是NULL

26 个答案:

答案 0 :(得分:46)

我现在正在寻找解决问题的方法,所以我会分享我的解决方案。

您的模型需要有一个空/默认构造函数,否则显然无法创建模型。 重构时要小心。 ;)

答案 1 :(得分:46)

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

  1. {"name":"John Doe", "age":18, "country":"United States of America"}作为json尝试您的请求。
  2. 删除[FromBody]属性并尝试解决方案。它应该适用于非原始类型。 (学生)
  3. 使用[FromBody]属性,另一个选项是以=Value格式而不是key=value格式发送值。这意味着您的student键值应为空字符串......
  4. 还有其他选项可以为学生类编写自定义模型绑定器,并使用自定义绑定器对参数进行属性化。

答案 2 :(得分:25)

我花了几个小时处理这个问题...... :(在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; }
}

答案 3 :(得分:13)

这是一个有点旧的,我的回答将归结为最后一个地方,但即便如此,我想分享我的经验。

尝试了所有建议,但在PUT [FromBody]中仍然具有相同的“null”值。

最后发现,当JSON序列化我的Angular对象的EndDate属性时,它就是Date格式。

没有抛出任何错误,只是收到了一个空的FromBody对象....

答案 4 :(得分:12)

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

例如,如果json中的 age 属性值为float

  

"年龄":18.0

但API服务期望它是int

  

"年龄":18

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

答案 5 :(得分:7)

如果使用Postman,请确保:

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

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

答案 6 :(得分:6)

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

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

例如,参数为null的最典型原因之一是请求正文的语法无效(例如JSON无效)。在这种情况下,合理的API将返回400 BAD REQUEST,合理的Web框架将自动执行此操作。但是,ASP.NET Web API在这方面是不合理的。只需将参数设置为null,然后请求处理程序就需要“手动”代码来检查参数是否为null

因此,此处给出的许多答案在错误处理方面都是不完整的,有缺陷的或恶意的客户端可能会通过发送无效的请求而在服务器端导致意外行为,(在最佳情况下)将抛出{{ 1}},并返回错误状态NullReferenceException,或者更糟的是,执行意外操作或崩溃或暴露安全漏洞。

一种适当的解决方案是编写一个自定义“ 500 INTERNAL SERVER ERROR”属性,该属性可以进行正确的错误处理并返回正确的状态代码,理想情况下还应包含一些诊断信息以帮助客户开发人员。

一种可能有帮助(尚未测试)的解决方案是要求提供参数,如下所示:https://stackoverflow.com/a/19322688/2279059

以下笨拙的解决方案也适用:

[FromBody]

(希望)可以正确处理错误,但声明性较差。例如,如果使用Swagger记录您的API,则它将不知道参数类型,这意味着您需要找到一些手动的解决方法来记录您的参数。这只是为了说明// 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 ... } 应该做什么。

编辑:不太笨拙的解决方案是检查[FromBody]https://stackoverflow.com/a/38515689/2279059

编辑:如果将ModelStateModelState.IsValid一起使用并且缺少参数,则似乎没有像预期的那样将false设置为JsonProperty。所以这也没用。

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

答案 7 :(得分:3)

我也试图使用[FromBody],但是,我试图填充一个字符串变量,因为输入将会改变,我只需要将它传递给后端服务,但这总是为空

Post([FromBody]string Input]) 

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

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

这很有效。

答案 8 :(得分:2)

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

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

答案 9 :(得分:2)

将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 :(得分:1)

我只是碰到这个而感到沮丧。 我的设置: 标头设置为 内容类型:应用程序/ JSON 并且正在以JSON格式从主体传递信息,并正在读取控制器上的[FromBody]。

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

一个技巧,我想出的办法是将数据部分发送到我的控制器,并检查它何时为空...因为这是一个复杂的模型,所以我一次将一个模型追加到我的请求参数中。希望它能帮助遇到这个愚蠢问题的人。

答案 11 :(得分:1)

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

答案 12 :(得分:1)

我使用HttpRequestMessage,经过大量研究后问题得以解决

com.netflix.nebula:gradle-ospackage-plugin:5.1.0

答案 13 :(得分: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对象调用服务,该对象被意外序列化了两次!)

答案 14 :(得分:1)

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

答案 15 :(得分:1)

在我的情况下,问题是我发送的DateTime对象。我用{yyyy-MM-dd'创建了一个DateTime,并且我映射到的对象所需的DateTime也需要“HH-mm-ss”。因此附加“00-00”解决了问题(由于这个原因,完整项目为空)。

答案 16 :(得分: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 :每次我将空字符串作为Id(天真地将它自动转换为Guid.Empty)从我的前端传递时我收到了空对象{{ 1}}参数。

解决方案要么

  • 传递有效[FromBody]
  • 或将Guid更改为Guid

答案 17 :(得分: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正在被传递给客户端,但是当时间将对象传递回服务器时,它只有空字段(空/默认值)。删除“内部”会使事情井井有条,允许反序列化机制编写对象的属性。

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

答案 18 :(得分:1)

我遇到了同样的问题。

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

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

答案 19 :(得分:0)

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

我在此网站上使用了其中一种解决方案

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

答案 20 :(得分:0)

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

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

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

答案 21 :(得分:0)

如果这是因为Web API 2由于数据类型不匹配而遇到反序列化问题,则可以通过检查内容流找出失败的位置。它将在读取错误之前进行读取,因此如果您将内容作为字符串读取,则应该使用您发布的数据的后半部分:

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

修复该参数,下次应该进一步(如果幸运,也可以成功!)...

答案 22 :(得分:0)

似乎这个问题有很多不同的原因......

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

using System.Runtime.Serialization;

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

答案 23 :(得分:0)

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

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

...

答案 24 :(得分:0)

在我的情况下( .NET Core 3.0 ),我必须使用 AddNewtonsoftJson()配置JSON序列化来解析camelCase属性:

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

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

答案 25 :(得分:-1)

再看一遍...我的模型被标记为[可序列化],这是导致失败的原因。