为同一端点使用任何类型的内容类型

Posted

技术标签:

【中文标题】为同一端点使用任何类型的内容类型【英文标题】:Consume any type of content-type for same endpoint 【发布时间】:2019-02-07 15:56:29 【问题描述】:

我有一个 asp.net core (v2.1) webapi 项目,它公开了这个功能:

[HttpPost]
[Route("v1/do-something")]
public async Task<IActionResult> PostDoSomething(ModelData model)

    //...

还有这个模型:

public class ModelData

    [Required]
    public string Email  get; set; 

我想让这个端点灵活,从内容类型的角度来看。因此,应该可以在正文中发送此端点不同的内容类型。

例如,那些“BODY”参数将被允许:

// application/x-www-form-urlencoded
email="abc123@gmail.com"

// application/json

    "email": "abc123@gmail.com"

与旧的 .net 框架相比,在 dotnet core 中这是不允许开箱即用的。我发现我需要添加Consume 属性和[FormForm] 属性。但是,如果我在模型参数中添加 [FormForm] 属性,它就不再适用于 JSON(例如)——因为它应该是 [FromBody]

我认为使用这样的代码是可以的:

[HttpPost]
[Route("v1/do-something")]
public async Task<IActionResult> PostDoSomething([FromBody] [FromForm] ModelData model)

    //...

但正如您所料,此代码不起作用。

因此,为了实现这种灵活性,我必须复制所有端点——这听起来是个非常糟糕的主意。

[HttpPost]
[Route("v1/do-something")]
[Consume ("application/json")]
public async Task<IActionResult> PostDoSomething([FromBody] ModelData model)

    //...


[HttpPost]
[Route("v1/do-something")]
[Consume ("application/x-www-form-urlencoded")]
public async Task<IActionResult> PostDoSomething([FromForm] ModelData model)

    //...


// ... Other content types here ...

这听起来很容易。但似乎更复杂。

我错过了什么?如何使端点以任何内容类型工作?

【问题讨论】:

【参考方案1】:

这是基于内容类型绑定的a custom model binder。

public class BodyOrForm : IModelBinder

    private readonly IModelBinderFactory factory;

    public BodyOrForm(IModelBinderFactory factory) => this.factory = factory;

    public async Task BindModelAsync(ModelBindingContext bindingContext)
    
        var contentType = 
            bindingContext.ActionContext.HttpContext.Request.ContentType;

        BindingInfo bindingInfo = new BindingInfo();
        if (contentType == "application/json")
        
            bindingInfo.BindingSource = BindingSource.Body;
        
        else if (contentType == "application/x-www-form-urlencoded")
        
            bindingInfo.BindingSource = BindingSource.Form;
        
        else
        
            bindingContext.Result = ModelBindingResult.Failed();
        

        var binder = factory.CreateBinder(new ModelBinderFactoryContext
        
            Metadata = bindingContext.ModelMetadata,
            BindingInfo = bindingInfo,
        );

        await binder.BindModelAsync(bindingContext);
    

通过以下操作进行了测试。

[HttpPost]
[Route("api/body-or-form")]
public IActionResult PostDoSomething([ModelBinder(typeof(BodyOrForm))] ModelData model)

    return new OkObjectResult(model);

这是demo is on GitHub。

【讨论】:

太棒了!一个完美的解决方案。

以上是关于为同一端点使用任何类型的内容类型的主要内容,如果未能解决你的问题,请参考以下文章

挂钩烫发不止一种内容类型

RestEasy 客户端提供找不到内容类型应用程序/xml 类型的编写器

MFC中输出String类型(内容为01串)出现乱码

Sharepoint rest api - 创建字段并添加到所有内容类型

将 JTextPane 设置为内容类型 HTML 并使用字符串构建器

GraphIql apollo 引擎不支持的内容类型