Home > Software design >  Minimal API - 415 error when using multipart/form content type and custom body model
Minimal API - 415 error when using multipart/form content type and custom body model

Time:08-24

First of all, here is a POST mapping that DOES work :

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

The MapUploadSingleFileEndpoint method uses the entire body to get the file like so :

using var reader = new StreamReader(request.Body, Encoding.UTF8);

This works flawlessly from the Swagger UI, it shows the 2 parameters plus a file selection dialog and hitting execute returns a 200. I can then copy the file locally on the server and manipulate it at will. Do note that changing form-data to ANYTHING else results in Swagger not showing a file section dialog.

Here is the issue. I need an endpoint that takes the same parameters except it needs 2 files to work. Because I'm reading the whole body to get a single file in the previous method I can obviously not do the same here. Even if that wasn't the case the IFormFile type produces a file selector dialog that only allows a single selection. I tried changing the accept to IFormFileCollection or List<IFormFile> but that does not work, there is no file chooser on the Swagger UI. I decided to try creating this custom request model :

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

    public string NewExternalSystemName { get; set; }

    public IFormFile FirstFile { get; set; }

    public IFormFile SecondFile { get; set; }
}

I also added the following endpoint mapping :

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

            }).Accepts<MultipleFormularyFilesRequest>("multipart/form-data")
            .Produces(StatusCodes.Status200OK)
            .Produces(StatusCodes.Status400BadRequest);

This DOES result in Swagger UI having 4 parameters under request body and 2 file selectors. Unfortunately when I hit execute I get this 415 error :

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)

How can I get this to work?

CodePudding user response:

This is because FromBody binding is not supported in .NET Core Minimal APIs. As an alternative, you can customize the parameter binding. Here is the documentation. Here is an example code.

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 ValueTask<MultipleFormularyFilesRequest?> BindAsync(HttpContext context,
                                                   ParameterInfo parameter)
    {
        var firstFile = context.Request.Form.Files["FirstFile"];
        var secondFile = context.Request.Form.Files["SecondFile"];
        var oldExternalSystemName = context.Request.Form["OldExternalSystemName"];
        var newExternalSystemName = context.Request.Form["NewExternalSystemName"];
        return ValueTask.FromResult<MultipleFormularyFilesRequest?>(new MultipleFormularyFilesRequest
        {
            FirstFile = firstFile,
            SecondFile = secondFile,
            OldExternalSystemName = oldExternalSystemName,
            NewExternalSystemName = newExternalSystemName
        });
    }
}
  • Related