如何在ASP.NET Core中结合FromBody和FromForm BindingSource?

时间:2018-08-03 13:06:21

标签: c# asp.net-core

我创建了一个新的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来支持两者发送数据的类型。 (尽管也许符合某些人的口味,包括我自己的口味,但不同的路线/路线可能更合适。)

3 个答案:

答案 0 :(得分:5)

您可以通过自定义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主题中有更多详细信息。

答案 1 :(得分:4)

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

答案 2 :(得分:0)

裸包Toycloud.AspNetCore.Mvc.ModelBinding.BodyAndFormBinding 应该符合您的需求

github