最小 API - 使用多部分/表单内容类型和自定义正文模型时出现 415 错误

Mar*_*tin 1 c# http-status-code-415 asp.net-core .net-6.0 minimal-apis

首先,这是一个有效的 POST 映射:

app.MapPost("formulary/uploadSingle/{oldSys}/{newSys}",
            async (HttpRequest request, string oldSys, string newSys) =>
            {
                return await MapUploadSingleFileEndpoint(request, oldSys, newSys);

            }).Accepts<IFormFile>("form-data")
            .Produces(StatusCodes.Status200OK)
            .Produces(StatusCodes.Status400BadRequest);
Run Code Online (Sandbox Code Playgroud)

MapUploadSingleFileEndpoint 方法使用整个主体来获取文件,如下所示:

using var reader = new StreamReader(request.Body, Encoding.UTF8);
Run Code Online (Sandbox Code Playgroud)

这在 Swagger UI 中完美运行,它显示 2 个参数以及一个文件选择对话框,点击执行返回 200。然后我可以在服务器本地复制文件并随意操作它。请注意,将表单数据更改为任何其他内容都会导致 Swagger 不显示文件部分对话框。

这就是问题所在。我需要一个采用相同参数的端点,只是它需要 2 个文件才能工作。因为我正在阅读整个正文以在前面的方法中获取单个文件,所以我显然不能在这里做同样的事情。即使情况并非如此,IFormFile 类型也会生成一个仅允许单个选择的文件选择器对话框。我尝试将接受更改为IFormFileCollection或,List<IFormFile>但这不起作用,Swagger UI 上没有文件选择器。我决定尝试创建这个自定义请求模型:

public class MultipleFormularyFilesRequest
{
    public string OldExternalSystemName { get; set; }

    public string NewExternalSystemName { get; set; }

    public IFormFile FirstFile { get; set; }

    public IFormFile SecondFile { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我还添加了以下端点映射:

app.MapPost("formulary/uploadMultiple",
            async (MultipleFormularyFilesRequest request) =>
            {
                int i = 0;

            }).Accepts<MultipleFormularyFilesRequest>("multipart/form-data")
            .Produces(StatusCodes.Status200OK)
            .Produces(StatusCodes.Status400BadRequest);
Run Code Online (Sandbox Code Playgroud)

这确实会导致 Swagger UI 在请求正文下有 4 个参数和 2 个文件选择器。不幸的是,当我点击执行时,我收到此 415 错误:

Microsoft.AspNetCore.Http.BadHttpRequestException: Expected a supported JSON media type but got "multipart/form-data; boundary=----WebKitFormBoundaryUzAAKF4zHMbAvtpr".
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.Log.UnexpectedContentType(HttpContext httpContext, String contentType, Boolean shouldThrow)
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.<>c__DisplayClass46_3.<<HandleRequestBodyAndCompileRequestDelegate>b__2>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Run Code Online (Sandbox Code Playgroud)

我怎样才能让它发挥作用?

Anu*_*raj 5

这是因为 .NET Core Minimal API 不支持 FromBody 绑定。作为替代方案,您可以自定义参数绑定。这是文档。这是一个示例代码。

public class MultipleFormularyFilesRequest
{
    public string? OldExternalSystemName { get; set; }

    public string? NewExternalSystemName { get; set; }

    public IFormFile? FirstFile { get; set; }

    public IFormFile? SecondFile { get; set; }

    public static async ValueTask<MultipleFormularyFilesRequest?> BindAsync(HttpContext context,
                                                   ParameterInfo parameter)
    {
        var form = await context.Request.ReadFormAsync();
        var firstFile = form.Files["FirstFile"];
        var secondFile = form.Files["SecondFile"];
        var oldExternalSystemName = form["OldExternalSystemName"];
        var newExternalSystemName = form["NewExternalSystemName"];
        return new MultipleFormularyFilesRequest
        {
            FirstFile = firstFile,
            SecondFile = secondFile,
            OldExternalSystemName = oldExternalSystemName,
            NewExternalSystemName = newExternalSystemName
        });
    }
}
Run Code Online (Sandbox Code Playgroud)