Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.Net Core API Controller Accept Form or Body Data

I have an API action and I want it to accept either Form Data or raw JSON.

Here is my function

    [HttpPost]
    public async Task<IActionResult> ExternalLogin([FromForm]ExternalLoginModel formModel, [FromBody]ExternalLoginModel bodyModel)
    {

So I want them to be able to send the same data in two different ways, when I try this method with form-data I get a 415 error. When I try it with raw JSON it works fine.

I would like to be able to keep it in one function but if I have to break it out into two so be it.

like image 577
A. Hasemeyer Avatar asked Feb 26 '19 13:02

A. Hasemeyer


1 Answers

Unfortunately, if the body model binder can't parse the request body, it short circuits the request instead of simply skipping the binding to the action parameter.

If you really wanted to handle both content types in one action, you could implement an input formatter for the body model binder that bypasses this behavior. It can do so by pretending the binding was successful, if the request has a form content type.

The formatter itself is simple:

/// <summary>
/// This input formatter bypasses the <see cref="BodyModelBinder"/> by returning a null result, when the request has a form content type.
/// When registered, both <see cref="FromBodyAttribute"/> and <see cref="FromFormAttribute"/> can be used in the same method.
/// </summary>
public class BypassFormDataInputFormatter : IInputFormatter
{
    public bool CanRead(InputFormatterContext context)
    {
        return context.HttpContext.Request.HasFormContentType;
    }

    public Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
    {
        return InputFormatterResult.SuccessAsync(null);
    }
}

In your startup class, the formatter needs to be added:

services.AddMvc(options =>
{
    options.InputFormatters.Add(new BypassFormDataInputFormatter());
});

In your action you would still need to check which of the two parameters actually have been populated:

[HttpPost]    
public async Task<IActionResult> ExternalLogin([FromForm] ExternalLoginModel formModel, [FromBody] ExternalLoginModel bodyModel)
{
    ExternalLoginModel model;

    // need to check if it is actually a form content type, as formModel may be bound to an empty instance
    if (Request.HasFormContentType && formModel != null)
    {
        model = formModel;
    }
    else if (bodyModel != null)
    {
        model = bodyModel;
    }

    ...
like image 74
Redstone Avatar answered Oct 03 '22 17:10

Redstone