如何在ASP.NET Core中结合FromBody和FromForm BindingSource? [英] How to combine FromBody and FromForm BindingSource in ASP.NET Core?

查看:352
本文介绍了如何在ASP.NET Core中结合FromBody和FromForm BindingSource?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I've created a fresh ASP.NET Core 2.1 API project, with a Data dto class and this controller action:

[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; }
}

它应该将数据回显给用户,没有什么幻想.但是,这两个属性中只有一个起作用,具体取决于顺序.

It should echo the data back to the user, nothing fancy. However, only one of the two attributes works, depending on the order.

这是测试要求:

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"
}'

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

I've tried several approaches, all to no avail:

  • 创建自定义子级BindingSource,但这似乎只是元数据.
  • 使用属性[CompositeBindingSource(...)],但是构造函数是私有的,因此可能不打算使用
  • 为此创建一个IModelBinder和提供程序,但是(1)我可能只希望在特定的控制器操作上执行此操作,并且(2)似乎需要大量工作才能获得两个内部模型绑定程序(用于Body和FormCollection)
  • Creating a custom child BindingSource, but that's only metadata it seems.
  • Using an attribute [CompositeBindingSource(...)], but the constructor is private and this might not be intended usage
  • Creating an IModelBinder and provider for this, but (1) I might only want this on specific controller actions and (2) it's really a lot of work seemingly to get the two inner model binders (for Body and FormCollection)

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

So, what is the correct way to combine FromForm and FromBody (or I guess any other combination of sources) attributes into one?

要弄清这背后的原因,并解释为什么我的问题不是此问题的重复:我想要知道如何具有相同的URI/路由以支持两种不同类型的发送数据. (尽管也许符合某些人的口味,包括我自己的口味,但不同的路线/路线可能更合适.)

To clarify the reason behind this, and to explain why my question is not a duplicate of this question: I want to know how to have the same URI / Route to support both different types of sending data. (Even though perhaps to some folks' taste, including possibly my own, different routes/uris might be more appropriate.)

推荐答案

使用自定义

You might be able to achieve what you're looking for with a custom IActionConstraint:

从概念上讲,IActionConstraint是一种重载形式,但它是在具有相同URL的操作之间重载,而不是重载具有相同名称的方法.

Conceptually, IActionConstraint is a form of overloading, but instead of overloading methods with the same name, it's overloading between actions that match the same URL.

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

I've had a bit of a play with this and have come up with the following IActionConstraint implementation:

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

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

如您所见,这非常简单-它只是检查传入的HTTP请求是否为内容类型形式.为了使用此功能,您可以为相关操作指定属性.这是一个完整的示例,其中还包括此 answer ,但使用您的操作:

As you can see, it's very simple - it's just checking whether or not the incoming HTTP request is of a form content-type. In order to use this, you can attribute the relevant action. Here's a complete example that also includes the idea suggested in this answer, but using your action:

[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);

由于使用了[ApiController],因此

[FromBody]在上面是可选的,但在示例中我将其包含为明确内容.

[FromBody] is optional above, due to the use of [ApiController], but I've included it to be explicit in the example.

也来自文档:

...带有IActionConstraint的动作总是比不带有IActionConstraint的动作好.

...an action with an IActionConstraint is always considered better than an action without.

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

This means that when the incoming request is not of a form content-type, the FormContentType attribute I've shown will exclude that particular action and therefore use the PostFromBody. Otherwise, if it is of a form content-type, the PostFromForm action will win due to it being "considered better".

我已经在相当基本的水平上进行了测试,它确实可以满足您的需求.在某些情况下,它可能不太适合,所以我鼓励您尝试一下它,看看可以在哪里使用.我完全希望您会发现它完全崩溃的情况,但是仍然值得探索.

I've tested this at a fairly basic level and it does appear to do what you're looking for. There may be cases where it doesn't quite fit so I'd encourage you to have a play with it and see where you can go with it. I fully expect that you may find a case where it falls over completely, but it's an interesting idea to explore nonetheless.

最后,如果您不想使用某个属性,则可以配置一个约定,例如使用反射来查找具有[FromForm]属性的动作并自动添加约束.这篇出色的帖子关于该主题.

Finally, if you don't like having to use an attribute, it is possible to configure a convention that could e.g. use reflection to find actions with a [FromForm] attribute and automatically add the constraint. There are more details in this excellent post on the topic.

这篇关于如何在ASP.NET Core中结合FromBody和FromForm BindingSource?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆