.NET 6 Minimal API and multipart/form-data

.net-6.0, asp.net-core, minimal-apis, multipartform-data, rest

Solution

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.

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 `record`s.

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

Problem

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?

Original source