【问题标题】:Post JSON data to Dotnet Core webapi将 JSON 数据发布到 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 在哪里?查询参数?
  • 我们也可以说它在正文中。

标签: json asp.net-core asp.net-core-webapi


【解决方案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】:
  1. 如果在 JSON 文档中传递 param1,则必须在 TokenRequest 类中定义 param1。你得到的错误已经是这个意思了。

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

  3. 传递 param1
  4. 我更喜欢将 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 的工作原理。
猜你喜欢
  • 2022-01-09
  • 2017-06-05
  • 2018-11-22
  • 2014-07-23
  • 1970-01-01
  • 2018-07-12
  • 2022-01-16
  • 1970-01-01
  • 2019-02-14
相关资源
最近更新 更多