为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

C#
胡说叔叔 2021-11-14 15:06:53
我创建了一个新的 ASP.NET Core 2.1 API 项目,带有一个Datadto 类和这个控制器操作:[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"}'我尝试了几种方法,都无济于事:创建自定义 child BindingSource,但这似乎只是元数据。使用属性[CompositeBindingSource(...)],但构造函数是私有的,这可能不是预期用途IModelBinder为此创建一个和提供程序,但是 (1) 我可能只希望在特定的控制器操作上使用它,并且 (2) 获得两个内部模型绑定器(用于 Body 和 FormCollection)似乎确实需要做很多工作那么,将属性FromForm和FromBody(或我猜任何其他来源的组合)属性合二为一的正确方法是什么?澄清这背后的原因,并解释为什么我的问题不是这个问题的重复:我想知道如何使用相同的 URI/路由来支持两种不同类型的发送数据。(尽管可能对某些人的口味,包括我自己的口味,不同的路线/uri 可能更合适。)
查看完整描述

3 回答

?
喵喔喔

TA贡献1735条经验 获得超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 请求是否属于表单内容类型。为了使用它,您可以归因于相关操作。这是一个完整的示例,其中还包含此答案中建议的想法,但使用您的操作:


[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]属性的操作并自动添加约束。这篇关于该主题的优秀文章中有更多详细信息。


查看完整回答
反对 回复 2021-11-14
?
呼唤远方

TA贡献1856条经验 获得超11个赞

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


查看完整回答
反对 回复 2021-11-14
?
幕布斯7119047

TA贡献1794条经验 获得超8个赞

我喜欢接受的答案中提出的解决方案,甚至使用了一段时间,但现在我们有了这个[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


查看完整回答
反对 回复 2021-11-14
  • 3 回答
  • 0 关注
  • 1314 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信