.NET 6 最小 API 和 multipart/form-data
.NET 6 Minimal API and multipart/form-data
使用 .NET 6 Minimal API,我试图在 POST 方法中处理 multipart/form-data
。但是,使用以下代码:
app.MapPost("/tickets", async (IFreshdeskApiService s, [FromForm] CreateTicketDto dto) => await s.Add(dto))
.Accepts<CreateTicketDto>("multipart/form-data");
我收到 400 错误请求,正文为:
{
"error": "Expected a supported JSON media type but got \"multipart/form-data; boundary=--------------------------391539519671819893009831\"."
}
我切换到非最小 API(即使用 app.MapControllers()
),但是有什么方法可以在最小 API 中处理这个问题吗?
请参阅 Minimal APIs overview 的显式参数绑定部分:
Binding from form values is not supported in .NET 6.
大概他们的意思是最小的 APIs,因为 Model Binding in ASP.NET Core 中没有提到这一点。因此,不幸的是,.NET 6 不支持使用 [FromForm]
属性和从表单绑定,至少在最小 API 中是这样(我还没有测试其他场景)。
自定义模型绑定解决方法
有一个使用自定义模型绑定的解决方法。这受到本·福斯特 post Custom Model Binding in ASP.NET 6.0 Minimal APIs 的启发。基本思想是使用以下签名向 type/class 添加 BindAsync
方法:
public static ValueTask<TModel?> BindAsync(HttpContext httpContext, ParameterInfo parameter)
对于您的示例,我创建了一个简单的 record
,其中包含 3 个属性 Id
、Name
和 Status
。然后使用 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"]
)
);
}
}
现在您可以使用 FormData 将数据发送到 API 而不会收到错误。
就个人而言,我会从端点中删除 [FromForm]
属性,但是,在我的测试中,无论有没有它,它都可以工作。上述技术也适用于 class
类型,而不仅仅是 record
s.
更简单的选择
一个更简单的实现是将 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);
});
使用 .NET 6 Minimal API,我试图在 POST 方法中处理 multipart/form-data
。但是,使用以下代码:
app.MapPost("/tickets", async (IFreshdeskApiService s, [FromForm] CreateTicketDto dto) => await s.Add(dto))
.Accepts<CreateTicketDto>("multipart/form-data");
我收到 400 错误请求,正文为:
{
"error": "Expected a supported JSON media type but got \"multipart/form-data; boundary=--------------------------391539519671819893009831\"."
}
我切换到非最小 API(即使用 app.MapControllers()
),但是有什么方法可以在最小 API 中处理这个问题吗?
请参阅 Minimal APIs overview 的显式参数绑定部分:
Binding from form values is not supported in .NET 6.
大概他们的意思是最小的 APIs,因为 Model Binding in ASP.NET Core 中没有提到这一点。因此,不幸的是,.NET 6 不支持使用 [FromForm]
属性和从表单绑定,至少在最小 API 中是这样(我还没有测试其他场景)。
自定义模型绑定解决方法
有一个使用自定义模型绑定的解决方法。这受到本·福斯特 post Custom Model Binding in ASP.NET 6.0 Minimal APIs 的启发。基本思想是使用以下签名向 type/class 添加 BindAsync
方法:
public static ValueTask<TModel?> BindAsync(HttpContext httpContext, ParameterInfo parameter)
对于您的示例,我创建了一个简单的 record
,其中包含 3 个属性 Id
、Name
和 Status
。然后使用 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"]
)
);
}
}
现在您可以使用 FormData 将数据发送到 API 而不会收到错误。
就个人而言,我会从端点中删除 [FromForm]
属性,但是,在我的测试中,无论有没有它,它都可以工作。上述技术也适用于 class
类型,而不仅仅是 record
s.
更简单的选择
一个更简单的实现是将 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);
});