.NET 6 最小 API 和多部分/表单数据

Dar*_*iak 9 rest multipartform-data asp.net-core .net-6.0 minimal-apis

使用 .NET 6 Minimal API,我尝试multipart/form-data在 POST 方法中进行处理。但是,使用以下代码:

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

我收到 400 错误请求,正文为:

{
    "error": "Expected a supported JSON media type but got \"multipart/form-data; boundary=--------------------------391539519671819893009831\"."
}
Run Code Online (Sandbox Code Playgroud)

我切换到非最小API(即使用app.MapControllers()),但是有什么方法可以在最小API中处理这个问题吗?

hal*_*ldo 19

请参阅最小 API 概述的显式参数绑定部分:

.NET 6支持从表单值进行绑定。

因此,不幸的是,[FromForm].NET 6 的最小 API 不支持使用表单中的属性和绑定。


自定义模型绑定解决方法

有一个使用自定义模型绑定的解决方法。这受到 Ben Foster 的帖子《ASP.NET 6.0 Minimal APIs 中的自定义模型绑定》的启发。BindAsync基本思想是使用以下签名向您的类型/类添加一个方法:

public static ValueTask<TModel?> BindAsync(HttpContext httpContext, ParameterInfo parameter)
Run Code Online (Sandbox Code Playgroud)

对于您的示例,我创建了一个record具有 3 个属性的简单的Id,NameStatus。然后,您使用HttpContext.Request.Form集合从以下位置获取所需的值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"]
            )
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,您可以使用 FormData 将数据发送到 API,而不会收到错误。

显示邮递员成功的图像

[FromForm]就我个人而言,我会从端点中删除该属性,但是,在我的测试中,无论有没有它,它都可以工作。上述技术也适用于class类型,而不仅仅是records。


更简单的替代方案

更简单的实现是传递HttpContext到操作并从ctx.Request.Form集合中读取所有值。在这种情况下,您的操作可能如下所示:

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);
});
Run Code Online (Sandbox Code Playgroud)

  • 有一个开放的 github 问题:https://github.com/dotnet/aspnetcore/issues/39430,因此可能会在 .net 7 中重新添加对“[FromForm]”的支持 (4认同)