【问题标题】:How to combine FromBody and FromForm BindingSource in ASP.NET Core?如何在 ASP.NET Core 中结合 FromBody 和 FromForm BindingSource?
【发布时间】:2021-09-19 17:09:40
【问题描述】:

我创建了一个全新的 ASP.NET Core 2.1 API 项目,其中包含一个 Data dto 类和这个控制器操作:

[HttpPost]
public ActionResult<Data> Post([FromForm][FromBody] Data data)
{
    return new ActionResult<Data>(data);
}
public class Data
{
    public string Id { get; set; }
    public string Txt { get; set; }
}

它应该将数据回显给用户,没什么特别的。但是,这两个属性中只有一个有效,具体取决于顺序。

这是测试请求:

curl -X POST http://localhost:5000/api/values \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'id=qwer567&txt=text%20from%20x-www-form-urlencoded'

curl -X POST http://localhost:5000/api/values \
  -H 'Content-Type: application/json' \
  -d '{
    "id": "abc123",
    "txt": "text from application/json"
}'

我尝试了几种方法,都无济于事:

  • 创建自定义子 BindingSource,但这似乎只是元数据。
  • 使用属性[CompositeBindingSource(...)],但构造函数是私有的,这可能不是预期用途
  • 为此创建一个 IModelBinder 和提供程序,但是 (1) 我可能只希望在特定的控制器操作上使用它并且 (2) 看起来确实需要大量工作才能获得两个内部模型绑定器(用于 Body 和 FormCollection)

那么,将FromFormFromBody(或者我猜是任何其他来源的组合)属性组合成一个属性的正确方法是什么?

澄清这背后的原因,并解释为什么我的问题不是this question 的重复:我想知道如何使用相同的 URI / Route 来支持两种不同类型的发送数据。 (尽管可能按照某些人的口味,可能包括我自己的口味,不同的路线/uris 可能更合适。)

【问题讨论】:

  • 请问您为什么要这样做?我刚刚尝试过 bc 也许我认为你可以省略这两个属性并且它会起作用。表单请求仅适用于 [FromForm],而 json-request 不适用 [FromBody]。
  • 因为我想为消费者提供两种调用 API 的方式。 (至少,我认为这就是原因。其他人问过我同样的问题,此时我只想知道是为了知道:D)。 PS。如果我没记错的话,在最近的版本中省略 [FromBody] 是可行的,因为它是按照惯例假设的。
  • @Jeroen:你是对的,但仅当[ApiController] 属性应用于控制器时。对于使用该属性修饰的控制器,类参数被假定为隐式[FromBody]。但是,在这种情况下,如果您确实想要这样,则需要应用 [FromForm]
  • 这个answer(它是我的)可能有助于解释[ApiController] 方法并拆分@ChrisPratt 在他的回答中建议的两个动作(我已将其标记为可能重复)。
  • 在问题的底部查看我的编辑,解释为什么我认为它不是重复的。

标签: c# asp.net-core


【解决方案1】:

您可以通过自定义IActionConstraint 实现您想要的:

从概念上讲,IActionConstraint 是一种重载形式,但不是重载同名的方法,而是在匹配相同 URL 的操作之间重载。

我对此进行了一些尝试,并提出了以下IActionConstraint 实现:

public class FormContentTypeAttribute : Attribute, IActionConstraint
{
    public int Order => 0;

    public bool Accept(ActionConstraintContext ctx) =>
        ctx.RouteContext.HttpContext.Request.HasFormContentType;
}

如您所见,它非常简单——它只是检查传入的 HTTP 请求是否属于表单内容类型。为了使用它,您可以归因于相关操作。这是一个完整的示例,其中还包含此 answer 中建议的想法,但使用您的操作:

[HttpPost]
[FormContentType]
public ActionResult<Data> PostFromForm([FromForm] Data data) =>
    DoPost(data);

[HttpPost]
public ActionResult<Data> PostFromBody([FromBody] Data data) =>
    DoPost(data);

private ActionResult<Data> DoPost(Data data) =>
    new ActionResult<Data>(data);

[FromBody] 在上面是可选的,因为使用了[ApiController],但我已经在示例中明确包含它。

也来自文档:

...具有 IActionConstraint 的动作总是被认为比没有的动作更好。

这意味着当传入的请求不是表单内容类型时,我显示的FormContentType 属性将排除该特定操作,因此使用PostFromBody。否则,如果它表单内容类型,PostFromForm 操作将获胜,因为它“被认为更好”。

我已经在相当基本的水平上对此进行了测试,它似乎确实可以满足您的需求。可能在某些情况下它不太适合,所以我鼓励你玩一下它,看看你可以用它去哪里。我完全希望您可能会发现它完全倒下的情况,但这仍然是一个值得探索的有趣想法。

最后,如果您不喜欢使用属性,可以配置一个约定,例如使用反射查找具有[FromForm] 属性的操作并自动添加约束。在这个出色的post 中有更多关于该主题的详细信息。

【讨论】:

  • 我喜欢接受的答案说你不能做某事,而它下面的答案告诉你该怎么做。
【解决方案2】:

你不能。一个动作只能接受一个或另一个。为了解决这个问题,您可以简单地创建多个操作,一个带有[FromBody],一个没有。他们当然也需要单独的路由,因为属性的存在不足以区分重载。但是,您可以将操作的主体分解为两个操作都可以使用的私有方法,以至少保持内容干燥。

【讨论】:

    【解决方案3】:

    我喜欢接受的答案中提出的解决方案,甚至使用了一段时间,但现在我们有了[Consumes] 属性。

    您甚至可以将两者映射到同一个路由 URL,这是个好消息。

    [HttpPost]
    [Route("/api/Post")] //same route but different "Consumes"
    [Consumes("application/x-www-form-urlencoded")]
    public ActionResult Post([FromForm] Data data)
    {
        DoStuff();
    }
    
    [HttpPost]
    [Route("/api/Post")] //same route but different "Consumes"
    [Consumes("application/json")]
    public ActionResult PostJson([FromBody] Data data)
    {
        Post(data); //just call the other action method
    }
    

    https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-5.0#define-supported-request-content-types-with-the-consumes-attribute

    【讨论】:

      【解决方案4】:

      nuget 包 Toycloud.AspNetCore.Mvc.ModelBinding.BodyAndFormBinding 应该满足你的需求

      github

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-02-02
        • 2021-12-20
        • 1970-01-01
        • 2018-11-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多