.NET 6 Minimal API and multipart/form-data

Viewed 3900

Using the .NET 6 Minimal API, I'm trying to handle multipart/form-data in the POST method. However, with the following code:

app.MapPost("/tickets", async (IFreshdeskApiService s, [FromForm] CreateTicketDto dto) => await s.Add(dto))
   .Accepts<CreateTicketDto>("multipart/form-data");

I'm receiving 400 Bad Request with body:

{
    "error": "Expected a supported JSON media type but got \"multipart/form-data; boundary=--------------------------391539519671819893009831\"."
}

I switched to the non-minimal API (i.e. using app.MapControllers()), but is there any way to handle this in minimal API?

1 Answers

Please see the Explicit Parameter Binding section of Minimal APIs overview:

Binding from form values is not supported in .NET 6.

So, unfortunately, using [FromForm] attribute and binding from forms is not supported in .NET 6 in minimal APIs.


Custom Model Binding Workaround

There is a workaround using custom model binding. This was inspired by Ben Foster's post Custom Model Binding in ASP.NET 6.0 Minimal APIs. The basic idea is to add a BindAsync method to your type/class with the following signature:

public static ValueTask<TModel?> BindAsync(HttpContext httpContext, ParameterInfo parameter)

For your example I created a simple record with 3 properties Id, Name and Status. Then you use HttpContext.Request.Form collection to get the required values from the Request:

public record CreateTicketDto(int Id, string Name, string Status)
{
    public static ValueTask<CreateTicketDto?> BindAsync(HttpContext httpContext, ParameterInfo parameter)
    {
        // parse any values required from the Request
        int.TryParse(httpContext.Request.Form["Id"], out var id);

        // return the CreateTicketDto
        return ValueTask.FromResult<CreateTicketDto?>(
            new CreateTicketDto(
                id,
                httpContext.Request.Form["Name"],
                httpContext.Request.Form["Status"]
            )
        );
    }
}

Now you can send data to the API using FormData without receiving an error.

image showing success in postman

Personally, I would remove the [FromForm] attribute from the endpoint, however, in my testing it works with or without it. The above technique will work with class types too, not just records.


Simpler alternative

A simpler implementation is to pass HttpContext into the action and read all values from the ctx.Request.Form collection. In this case your action might look something like the following:

app.MapPost("/tickets", (HttpContext ctx, IFreshdeskApiService s) =>
{
    // read value from Form collection
    int.TryParse(ctx.Request.Form["Id"], out var id);
    var name = ctx.Request.Form["Name"];
    var status = ctx.Request.Form["Status"];

    var dto = new CreateTicketDto(id, name, status);
 
    s.Add(dto);
    return Results.Accepted(value: dto);
});
Related