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

我创建了一个新的 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 可能更合适。)


胡说叔叔
浏览 1318回答 3
3回答

喵喔喔

您可能能够通过自定义实现您正在寻找的内容IActionConstraint:从概念上讲,IActionConstraint 是一种重载形式,但它不是重载具有相同名称的方法,而是在匹配相同 URL 的操作之间进行重载。我对此进行了一些尝试,并提出了以下IActionConstraint实现:public class FormContentTypeAttribute : Attribute, IActionConstraint{&nbsp; &nbsp; public int Order => 0;&nbsp; &nbsp; public bool Accept(ActionConstraintContext ctx) =>&nbsp; &nbsp; &nbsp; &nbsp; ctx.RouteContext.HttpContext.Request.HasFormContentType;}如您所见,它非常简单——它只是检查传入的 HTTP 请求是否属于表单内容类型。为了使用它,您可以归因于相关操作。这是一个完整的示例,其中还包含此答案中建议的想法,但使用您的操作:[HttpPost][FormContentType]public ActionResult<Data> PostFromForm([FromForm] Data data) =>&nbsp; &nbsp; DoPost(data);[HttpPost]public ActionResult<Data> PostFromBody([FromBody] Data data) =>&nbsp; &nbsp; DoPost(data);private ActionResult<Data> DoPost(Data data) =>&nbsp; &nbsp; new ActionResult<Data>(data);[FromBody]由于使用了 ,因此在上面是可选的[ApiController],但我已经在示例中明确包含了它。也来自文档:...带有 IActionConstraint 的动作总是被认为比没有的动作更好。这意味着当传入的请求不是表单内容类型时,FormContentType我显示的属性将排除该特定操作,因此使用PostFromBody. 否则,如果它是表单内容类型,则该PostFromForm操作将获胜,因为它被“认为更好”。我已经在相当基本的水平上对此进行了测试,它似乎确实可以满足您的需求。可能有些情况下它不太合适,所以我鼓励你玩玩它,看看你可以用它去哪里。我完全希望您可能会发现它完全倒塌的情况,但仍然是一个值得探索的有趣想法。最后,如果您不喜欢必须使用属性,可以配置一个约定,例如使用反射来查找具有[FromForm]属性的操作并自动添加约束。这篇关于该主题的优秀文章中有更多详细信息。

呼唤远方

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

幕布斯7119047

我喜欢接受的答案中提出的解决方案,甚至使用了一段时间,但现在我们有了这个[Consumes]属性。你甚至可以将两者映射到同一个路由 URL,这是个好消息。[HttpPost][Route("/api/Post")] //same route but different "Consumes"[Consumes("application/x-www-form-urlencoded")]public ActionResult Post([FromForm] Data data){&nbsp; &nbsp; DoStuff();}[HttpPost][Route("/api/Post")] //same route but different "Consumes"[Consumes("application/json")]public ActionResult PostJson([FromBody] Data data){&nbsp; &nbsp; 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
打开App,查看更多内容
随时随地看视频慕课网APP