.NET 6 Minimal API and multipart/form-data [duplicate]
Asked Answered
Q

1

10

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?

Queeniequeenly answered 9/2, 2022 at 9:34 Comment(0)
M
19

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);
});
Maddalena answered 9/2, 2022 at 11:33 Comment(6)
Great, thank you for providing an alternative. It doesn't seem to be Minimal anymore, especially for handling multiple fields, but glad it works.Joanniejoao
There is an open github issue: github.com/dotnet/aspnetcore/issues/39430 so support for [FromForm] might be added back in .net 7Maddalena
This is now supported in .NET 8 Preview 6 onwardsUnconcern
@JaliyaUdagedara for me it seems that complex object are still not supported into -NET8 with FromForm in Minimal API. Could you show an example working in .NET8?Oklahoma
@LuigiSaggese, can you check this: jaliyaudagedara.blogspot.com/2023/10/…Unconcern
@JaliyaUdagedara yes if IFormFile is at call level it works, but if the IFormFile is part of a complex object it does not workOklahoma

© 2022 - 2025 — McMap. All rights reserved.