将 JSON 数据发布到 Dotnet Core webapi

Posted

技术标签:

【中文标题】将 JSON 数据发布到 Dotnet Core webapi【英文标题】:Post JSON data to Dotnet Core webapi 【发布时间】:2019-01-30 04:32:46 【问题描述】:

我正在使用 aspnet 核心开发一个 WebApi。我已经能够设置一个基本项目并且请求运行良好。

现在我正在尝试使用邮递员将复杂的 JSON 对象发布到 API。 API 代码如下:

//controller class
public class DemoController : ControllerBase 

    [HttpPost]
    public IActionResult Action1([FromBody]TokenRequest request) 
        /* This works. I get request object with properties populated */
    

    [HttpPost]
    public IActionResult Action2(TokenRequest request) 
        /* The request is received. But properties are null */
    


//TokenRequest Class
public class TokenRequest 
    public string Username  get; set; 
    public string Password  get; set; 

试图用邮递员进行测试。

请求 1:(Action1 和 Action2 均失败)

POST /Demo/Action2 HTTP/1.1
Host: localhost:5001
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 42642430-bbd3-49ca-a56c-cb3f5f2177cc


    "request": 
        "Username": "saurabh",
        "Password": "******"
    

请求 2:(Action1 成功,Action2 失败)

POST /User/Register HTTP/1.1
Host: localhost:5001
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 94141e01-7fef-4847-953e-a9acb4e6c445


    "Username": "saurabh",
    "Password": "******"

由于 [FromBody[ 标记,这些东西与 Action1 一起工作。但是如果我需要接受多个参数呢?喜欢

public IActionResult Action1(int param1, TokenRequest request)

选项 1:使用包装类(由 Francisco Goldenstein 建议)

[FromBody] 不能与两个不同的参数一起使用。有什么优雅的解决方案可以让我接受它们作为 Action 方法中的单独参数吗?

【问题讨论】:

param1 在哪里?查询参数? 我们也可以说它在正文中。 【参考方案1】:

另一种发布多个对象的解决方案是使用 Form 并将每个 json 对象发布在表单的分隔字段中。将JsonBinder 应用到它,我们可以像[FromBody] 一样在参数中使用模型。

public class FormDataJsonBinder : IModelBinder

    public Task BindModelAsync(ModelBindingContext bindingContext)
    
        if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));

        // Fetch the value of the argument by name and set it to the model state
        string fieldName = bindingContext.FieldName;
        var valueProviderResult = bindingContext.ValueProvider.GetValue(fieldName);
        if (valueProviderResult == ValueProviderResult.None) return Task.CompletedTask;
        else bindingContext.ModelState.SetModelValue(fieldName, valueProviderResult);

        // Do nothing if the value is null or empty
        string value = valueProviderResult.FirstValue;
        if (string.IsNullOrEmpty(value)) return Task.CompletedTask;

        try
        
            // Deserialize the provided value and set the binding result
            object result = JsonConvert.DeserializeObject(value, bindingContext.ModelType);
            bindingContext.Result = ModelBindingResult.Success(result);
        
        catch (JsonException)
        
            bindingContext.Result = ModelBindingResult.Failed();
        

        return Task.CompletedTask;
    

动作将是:

public IActionResult Action1(
    [FromForm][ModelBinder(BinderType = typeof(FormDataJsonBinder))] TokenRequest request,
    [FromForm][ModelBinder(BinderType = typeof(FormDataJsonBinder))] TokenRequest request1)

    return Ok();

虽然我会选择包装类选项。

【讨论】:

这可能有效,但解决简单问题相当复杂。 感谢您,我需要在隐藏的输入字段(由 angularjs 作为组件生成)中发布复杂的 JSON 作为整体表单提交的一部分,并且不想将其作为字符串接收然后该操作在控制器中反序列化它。【参考方案2】:

    如果在 JSON 文档中传递 param1,则必须在 TokenRequest 类中定义 param1。你得到的错误已经是这个意思了。

    您不必在 JSON 文档中传递 param1。您可以通过 Query params OR Http headers

    传递 param1

    我更喜欢将 param1 作为查询参数传递。

    POST /User/Register?param1=Param1

    POST /User/Register?param1=Param1 HTTP/1.1
    Host: localhost:5001
    Content-Type: application/json
    Cache-Control: no-cache
    Postman-Token: 94141e01-7fef-4847-953e-a9acb4e6c445
    
    
        "Username": "saurabh",
        "Password": "******"
    
    

public IActionResult Action1([FromQuery] int param1, [FromBody] TokenRequest request)

【讨论】:

看起来不错。我们可以在 Header 或 body 以外的其他地方有复杂的 JSON 对象吗?本例中的示例 TokenRequest。【参考方案3】:

如果您需要接受更多参数,则需要创建另一个包含您需要的所有内容的类。一个包装类,像这样:

public class X 

     public TokenRequest TokenRequest  get; set; 
     public int Param1  get; set; 

控制器的动作是这样的:

[HttpPost] 公共 IActionResult Action1([FromBody]X 请求) // 你的代码

在 ASP.NET Core 中,您需要指出请求的值是否是正文、表单等的一部分。这与 ASP.NET MVC 的工作方式不同,其中每个部分(正文、表单等)被考虑。

附注

您还可以考虑使用继承,定义一个从 TokenRequest 继承的新类,但这不是一个好习惯,因为新类型不尊重“是 TokenRequest”。最好说“有一个TokenRequest”。

【讨论】:

这是我的第一个想法。但是是否有可能在没有包装类的情况下处理这种情况? 不,这行不通。你需要包装器。我知道这是更多的课程,但在我看来它更干净。 好的。明天将在这方面尝试更多的东西。希望有别的办法。并感谢您的回答。如果我在一两天内找不到/锻炼任何其他解决方案,我将继续使用这个。 一个月前我将一个大型应用程序从 ASP.NET MVC 迁移到 ASP.NET Core MVC,所以我知道您面临什么。我希望微软写了一个迁移指南。昨天我不得不使用 ASP.NET MVC 编写一个 API 以继续使用没有 Core 版本的 PDF 库。祝你好运! 是的,即使我已经习惯了 ASP.NET MVC。新到核心。我想我需要更多地了解 http 的工作原理。

以上是关于将 JSON 数据发布到 Dotnet Core webapi的主要内容,如果未能解决你的问题,请参考以下文章

Dotnet Core 3.1:如何将 AddJsonFile 与文件的绝对路径一起使用?

dotnet core 5 Webapi 向 HTTPPOST 发送 Json 响应

dotnet core 不包括在launchSettings.json 中构建的测试项目

在 Visual Studio 16.6 中将仅内容包导入 DotNet Core 3.1 项目

使用appsettings.json配置Kestrel监听端口Dotnet core 2 preview 2

dotnet core webapi json-api 兼容查询字符串路由