ASP.NET 核心 MVC:如何将原始 JSON 绑定到没有类型的字符串?

ASP.NET Core MVC : How to get raw JSON bound to a string without a type?

类似于 this 关于先前 ASP.NET 版本的旧问题,我想获取 HTTP POST 的请求 body 以绑定到字符串。似乎该方法绑定了,但是 value 为空,当 ASP.NET 调用我的控制器方法时:

namespace Demo.Controllers
{

    [Route("[controller]")]
    public class WebApiDemoController : Controller
    {
    ...

    // POST api/values
    [HttpPost]
    public System.Net.Http.HttpResponseMessage Post([FromBody]string value)
    {
       // expected: value = json string, actual: json = null.
    }

我还需要从流中获取 body 吗?或者这应该有效吗?在测试上面的方法时,我使用了如下的httpheaders:

Accept: Application/json
Content-Type: Application/json;charset=UTF-8

我在 body 中传递以下内容:{ "a": 1 }

我不想绑定到名为 a 的字符串变量。我想绑定我得到的任何 JSON,然后我想在我的方法中使用 JSON 内容,任何任意内容。

如果我理解了文档,[FromBody] 属性应该完成了我想要的,但我猜测 ASP.NET 核心 MVC 绑定机制不会绑定 json 到 "string value",但也许我可以做一些其他事情来获得同等水平的灵活性。

A similar question here 让我想到也许我应该写 [FromBody] dynamic data 而不是使用 [FromBody] string value.

更新:这种技巧在做之前应该考虑一下,因为如果你想让.net核心框架为你处理JSON和XML编码,你就杀了那个能力。某些类型的 REST 服务器可以并且经常确实需要同时支持 XML 和 JSON content-types,至少我遇到过的那些有标准文档。

您需要一个类型来绑定数据。示例:

public class Person
{
   public string Name {get; set;}
}

数据{ "Name" : "James"}

如果您想接收字符串,您需要将其作为字符串传递。您的 JSON 应该用引号引起来:

'{ "a": 1 }'

以下内容适用于 .net 核心 1.x,但不适用于 .net 核心 2.x。

正如我评论的那样,解决方案是使用 [FromBody]dynamic data 作为我的参数列表,使用 dynamic 而不是 string,我将收到一个 JObject

注意:如果您的架构要求单个 WebApi 服务器在生成 XML 和 JSON 时同样流畅,具体取决于 content-type header 个条目,这种 direct-JSON-consumption 策略可能会适得其反。 (通过足够的工作,可以在同一服务上同时支持 XML 和 JSON,但是随后您将进一步向上移动 MVC 资产管道并将其向下移动到您的控制器方法中,这变成这违背了 MVC 的精神,在 MVC 中,模型以 POCO 已经解析的形式出现。)

一旦在方法内部转换为字符串,将传入的JObject(Newtonsoft.JSON在内存中的数据类型为JSON)转换为字符串。

发现于 other answer here

示例代码,感谢 Jeson Martajaya:

有动态:

[HttpPost]
public System.Net.Http.HttpResponseMessage Post([FromBody]dynamic value)
{
   //...
}

使用 JObject 的示例代码:

[HttpPost]
public System.Net.Http.HttpResponseMessage Post([FromBody]Newtonsoft.Json.Linq.JObject value)
{
   //...
}

或者,您也可以只接受 JObject,如果您真的需要字符串,您甚至可以直接 ToString() 使用 Linq Json。

我看到 Sam 已经因为说了几乎相同的话而被否决了,但是在使用 Postman 进行测试时我发现如果我将请求正文设置为一个简单的双引号字符串 ASP 就会绑定它可以使用默认的“[FromBody]字符串值”参数。

"just send your string like this without any curly braces"

不确定 application/json 是否应该接受这种格式的数据。希望通过发布这个知识渊博的人会管道并说明这是否有效。

我发现的最干净的选择是添加您自己的简单 InputFormatter:

public class RawJsonBodyInputFormatter : InputFormatter
{
    public RawJsonBodyInputFormatter()
    {
        this.SupportedMediaTypes.Add("application/json");
    }

    public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
    {
        var request = context.HttpContext.Request;
        using (var reader = new StreamReader(request.Body))
        {
            var content = await reader.ReadToEndAsync();
            return await InputFormatterResult.SuccessAsync(content);
        }
    }

    protected override bool CanReadType(Type type)
    {
        return type == typeof(string);
    }
}

并且在你的 Startup.cs 里面 ConfigureServices:

services
    .AddMvc(options =>
    {
        options.InputFormatters.Insert(0, new RawJsonBodyInputFormatter());
    });

这将使您获得控制器中的原始 JSON 有效负载:

[HttpPost]
public IActionResult Post([FromBody]string value)
{
    // value will be the request json payload
}

以下两种方法适用于 ASP.NET 核心 2 以读取原始 json 字符串。

1) 这个性能更好

    [HttpPost]
    public async Task<ActionResult<int>> Process()
    {
        string jsonString;
        using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
        {
            jsonString = await reader.ReadToEndAsync();
        }

2)

    [HttpPost]
    public async Task<ActionResult<int>> Process([FromBody]JToken jsonbody)
    {
        var jsonString = jsonBody.ToString();

基于 Saeb Amini 上面的出色回答,这将他的解决方案扩展为也适用于纯文本。这里唯一的变化是添加 "text/plain" mime 类型,并添加 namespace 和必需的 usings.

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Formatters;

namespace AspExtensions // or whatever
{
    // see: 
    public class RawStringBodyInputFormatter : InputFormatter
    {
        public RawStringBodyInputFormatter()
        {
            this.SupportedMediaTypes.Add("text/plain");
            this.SupportedMediaTypes.Add("application/json");
        }

        public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
        {
            var request = context.HttpContext.Request;
            using(var reader = new StreamReader(request.Body)) {
                string content = await reader.ReadToEndAsync();
                return await InputFormatterResult.SuccessAsync(content);
            }
        }

        protected override bool CanReadType(Type type)
        {
            return type == typeof(string);
        }
    }
}

找到 ASP.NET Core 3.1 Web API 的解决方案。

看起来如下:

public async Task<IActionResult> PutAsync([FromBody] System.Text.Json.JsonElement entity)
{ 
    // your code here
}

如果你不放弃自动绑定,这可以直接放在控制器上的 Http 处理程序中:

using StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8);
var value = reader.ReadToEndAsync().GetAwaiter().GetResult();