ASP.NET Core 3.0 [FromBody] 字符串内容返回“无法将JSON值转换为System.String。”

64
在 ASP.NET Core 3.0 中,在 ApiController 上使用 [FromBody] 字符串内容会返回验证错误。
{"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
 "title":"One or more validation errors occurred.",
 "status":400,
 "traceId":"|9dd96d96-4e64bafba4ba0245.",
 "errors":{"$":["The JSON value could not be converted to System.String. Path: $ | LineNumber: 0 | BytePositionInLine: 1."]}}

当客户端以内容类型:application/json 发送数据时,我该如何在.NET Core 3.0的API控制器中获取原始的JSON数据字符串?而无需客户端更新其内容类型。

1
这个在 .net core 2.1 中能用吗?请发一下你的 JSON 示例。 - cdev
1
我决定使用StreamReader并自己读取Request.Body。这是一个新项目,尚未在2.1上进行测试,但过去可能已将body绑定到JToken而不是字符串。 - Poul K. Sørensen
11个回答

73

不确定是否有帮助,但我认为他们在 .net core 3.0 中对 Newtonsoft.JSON 包进行了一些更改,因此您可以尝试以下操作:

安装 Microsoft.AspNetCore.Mvc.NewtonsoftJson 包。

在您的 startup.cs 文件中添加:

services.AddControllers().AddNewtonsoftJson();


6
3.0版本升级后,我的[FromBody] JSON参数返回了null。这个方法解决了问题。谢谢! - Graham Meehan
1
这绝对可行,在经过数小时的调试后,没有任何错误,只需安装即可完成! - Salem Kosemani
尝试使用asp .net core 3.1,但出现错误:“Package Microsoft.AspNetCore.Mvc.NewtonsoftJson 5.0.0与netcoreapp3.1不兼容”。 - Vikram Singh Saini
@Vikram,那是因为你正在安装NuGet包的5.0.0版本。在安装之前,请选择版本为3.1。 - Lukas
1
不适用于NET6。 - Leandro Bardelli
1
你刚刚帮我省去了将近200个api方法重构为使用类而不是JObjects的工作,我的兴奋难以压制。 - Ebikeneser

44
如果您正在使用ASP.NET Core 3.0+,那么它已经内置了对JSON的支持,使用的是System.Text.Json。我已经使用了以下方法,而且在不设置自定义输入处理程序的情况下也能正常工作。
[HttpPost]
public async Task<IActionResult> Index([FromBody] JsonElement body)
{

    string json = System.Text.Json.JsonSerializer.Serialize(body);
    return Ok();

}

1
不要忘记导入/使用 System.Text.Json; 以便识别 JsonElement。 - Benyamin Limanto
如果您使用System.Text.Json,它应该会自动添加;如果没有使用,Visual Studio应该会在将该行标记为错误时自动建议添加它。 - undefined
@TylerH 并不是每个人都在使用Visual Studio。 - undefined
1
@IS4 他们应该这样做,尤其是如果他们在编写ASP.NET代码。它甚至有一个免费版本。不过,无论如何,“包含提供所需功能的命名空间”都是不言而喻的。 - undefined

33
[FromBody] string content更改为[FromBody] object content,如果你想或需要将其读取为字符串,请使用content.ToString()

谢谢,我将输入参数类型更改为对象,然后进行了操作。 - Ali Ahmadvand
谢谢,我一直在苦苦挣扎。 - Amirreza

8

如果您将参数[FromBody] String value更改为[FromBody] YourType value,它将自动为您反序列化。

来自:

// POST api/<SelectiveCallRulesController>
[HttpPost]
public async Task Post([FromBody] String rule)        
{
...

至:

// POST api/<SelectiveCallRulesController>
[HttpPost]
public async Task Post([FromBody] SelectiveCallRule rule)        
{
...

我一直在转圈,直到意识到反序列化错误信息是正确的!


4
问题在于有时候你无法反序列化值,因为你不知道将要得到什么类型的对象。 - Eternal21

4
这个错误的原因是 "System.Text.Json 不会将非字符串值反序列化为字符串属性" (来源)。
这意味着如果你有一个控制器,它的简单参数是 [FromBody] string
[HttpPost("save")]
public async Task Save([FromBody] string content)
{

这个请求将成功:

curl -H "Content-Type: application/json" -X POST -d "\"abcdefgh\"" https://localhost:5000/save -v

但这会失败:

curl -H "Content-Type: application/json" -X POST -d "{\"content\":\"abcdefgh\"}" https://localhost:5000/save -v

实际上,不仅针对string类型,其他简单类型如int, bool等也会发生类似的错误。例如,在上述代码中将参数类型更改为int,然后在请求体中发送JSON {"content":123} 将会产生错误JSON value could not be converted to System.Int32
为了避免这种错误,可以采取以下措施之一:
  • 修复请求以将参数作为"some string"(而不是JSON)传递到请求体中
  • 将参数作为[FromQuery]或[FromForm]传递到请求中
  • 或者将您的参数移动到某个类的属性中(不要忘记为此成员添加getter和setter,因为类字段不会反序列化):
public class Content
{
    public string Value { get; set;}
}
...
[HttpPost("save")]
public async Task Save([FromBody] Content content)
{

在ASP.NET Core 7.0上进行了测试


感谢您深入的解释,帮助我轻松解决了我的问题。 - Honza P.

1
我不得不编写一个自定义的 IInputFormatter,以确保我的正文内容始终被解释为字符串。
我也曾经遇到过更新所有 API 客户端不可行的情况。
以下方法将确保任何 [FromBody] 参数都被解释为字符串,即使调用者没有用引号括起来。
public class JsonStringInputFormatter : TextInputFormatter
{
    public JsonStringInputFormatter() : base()
    {
        SupportedEncodings.Add(UTF8EncodingWithoutBOM);
        SupportedEncodings.Add(UTF16EncodingLittleEndian);

        SupportedMediaTypes.Add(MediaTypeNames.Application.Json);
    }

    public override bool CanRead(InputFormatterContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        return context.ModelType == typeof(string);
    }

    public override async Task<InputFormatterResult> ReadRequestBodyAsync(
        InputFormatterContext context, Encoding encoding)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        using (var streamReader = new StreamReader(
            context.HttpContext.Request.Body,
            encoding))
        {
            return await InputFormatterResult.SuccessAsync(
                (await streamReader.ReadToEndAsync()).Trim('"'));
        }
    }
}

从正文中删除引号可以使其对格式正确且带引号的正文内容具有前向兼容性。

确保在System.Text.Json格式化程序之前在您的启动中注册它:

services.AddControllers()
    .AddMvcOptions(options =>
    {
        options.InputFormatters.Insert(
            0,
            new JsonStringInputFormatter());
    });

0
在我的情况下,我正在使用Angular和NET 6.0进行工作。
因此,控制器:
    public string? Post([FromBody] string word)
    {
    }

以及来自Angular的调用:

使用

import { HttpClient, HttpHeaders } from '@angular/common/http';

代码:

const headers = new HttpHeaders({
  'Content-Type': 'application/json'
}); 

将请求标记为 JSON。

const body = JSON.stringify("myvalue");

  this.http.post(this.baseUrl + 'controller', body, { headers: headers, responseType: 'text', withCredentials: true }).subscribe(result => {
      this.mycontent = result;
    }, error => console.error(error));

在上面的例子中,responsetype是因为控制器还返回了一个字符串。

0
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;

     
[HttpPost]
        public IActionResult SaveScreen([FromBody] JObject value)
        {
            JObject result = new JObject();
            _log.LogInformation(JsonConvert.SerializeObject(value,Formatting.Indented));
            return Content(JsonConvert.SerializeObject(result), "application/json; charset=UTF-8");
        }

不确定这是否是您想要的,但我使用此代码并获得了所需的结果。我只想将JSON字符串发布到控制器中。


0

使用JsonElement代替字符串或对象。 {yourcontrollername([FromBody] JsonElement yourJsondata)}


0

你可以创建另一个类来包含你的 JSON 字段。


1
请您提供一个例子好吗?这个例子应该如何在请求正文中发布? - dwilli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接