代码之家  ›  专栏  ›  技术社区  ›  Jeroen

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

  •  1
  • Jeroen  · 技术社区  · 6 年前

    我创建了一个新的ASP.NET Core 2.1api项目,其中 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),似乎需要做很多工作

    那么,正确的组合方式是什么 FromForm FromBody (或者我猜其他来源的组合)属性合为一?

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

    2 回复  |  直到 6 年前
        1
  •  4
  •   Chris Pratt    6 年前

    你不能。一个动作只能接受一个或另一个。为了解决这个问题,您可以简单地创建多个操作,其中一个 [FromBody] 一个没有。当然,它们也需要单独的路由,因为属性的存在不足以区分重载。然而,你可以将你的行为体分解成一个私有的方法,这两个行为都可以利用,至少可以让事情保持干爽。

        2
  •  7
  •   Kirk Larkin    6 年前

    你也许可以通过一个定制 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的操作总是比没有IActionConstraint的操作好。

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

    我已经在一个相当基本的水平上测试过了,而且它看起来确实做到了你想要的。可能有些情况下,它不太适合,所以我鼓励你玩它,看看你可以去哪里。我完全希望你能找到一个完全失败的案例,但这是一个有趣的想法去探索。

    最后,如果您不喜欢使用属性,可以配置一个约定,例如使用反射来查找具有 [FromForm] 属性并自动添加约束。有更多的细节在这个优秀的 post 关于这个话题。