15

I want to create a simple file upload endpoint in ASP.NET Core 6 and thought it would be as easy as described here https://dotnetthoughts.net/handling-file-uploads-in-openapi-with-aspnet-core/.

When I have an endpoint defined like:

app.MapPost("/upload", (IFormFile file) =>
{
    //Do something with the file
    return Results.Ok();
}).Accepts<IFormFile>("multipart/form-data").Produces(200);

I get a 415 back when I call the endpoint. The message I get back is something like:

Expected a supported JSON media type but got "multipart/form-data; ...

Not sure why it expected a supported json when I say that the endpoint should accept multipart/form-data.

Any ideas or thoughts on what to do here?

1

4 Answers 4

20

Currently out of the box support for binding in Minimal APIs is quite limited. Supported binding sources:

  • Route values
  • Query string
  • Header
  • Body (as JSON)
  • Services provided by dependency injection
  • Custom

NOTE: Binding from forms is not natively supported in .NET 6

You can either leverage custom binding or use special types handling:

app.MapPost("/upload", (HttpRequest request) =>
{
    //Do something with the file
    var files = request.Form.Files;
    return Results.Ok();
})
.Accepts("multipart/form-data")
.Produces(200);

UPD

Since Minimal APIs should be able to bind IFormFile/IFormFileCollection directly:

app.MapPost("/upload", async (IFormFile file) =>
{
    // ...
});

app.MapPost("/upload_many", async (IFormFileCollection myFiles) =>
{
    foreach (var file in myFiles)
    {
        // ...
    }
});
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks. Will test tomorrow. I just wonder how they did it at the blog I referred to? Hm
@TomasJansson I'm pretty much sure they are not using Minimal APIs but "standard" Web APIs
Have you verified that this actually work? When I do it I get a Unexpected end of Stream, the content may have already been read by another component. exception. One difference is that I have Accepts<IFormFile>("multipart/form-data"), I guess that is what make it so asp.net tries to read the body. But I don't seem to have a non-generic Accepts to use.
Nevermind, I think it was how I created the request using the vs code rest client that was the problem, worked fine when testing the endpoint from insomnia.
9

Just noting here, you can upload a file with any ContentType like the following sample code.

In this example I chose a text/plain ContentType, but you can choose your own by editing the .Accepts<IFormFile>("text/plain"); line to your desired ContentType.

app.MapPost("/upload",
    async (HttpRequest request) =>
    {
        using (var reader = new StreamReader(request.Body, System.Text.Encoding.UTF8))
        {

            // Read the raw file as a `string`.
            string fileContent = await reader.ReadToEndAsync();

            // Do something with `fileContent`...
    
            return "File Was Processed Sucessfully!";
        }
    }).Accepts<IFormFile>("text/plain");

It is also well supported by Swagger UI:

enter image description here

1 Comment

If the file is binary I think its better to access the file via request.Form.Files[0] instead of reading from the body. Havent tested it though.
6

This has been addressed with .NET 7. Support for IFormFile and IFormFileCollection has been added. You should be able to use it in your MediatR pipelines.

Ref: .NET 7 Minimal API Support for File Upload Bindings

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapPost("/upload", async (IFormFile file) =>
{
    var tempFile = Path.GetTempFileName();
    app.Logger.LogInformation(tempFile);
    using var stream = File.OpenWrite(tempFile);
    await file.CopyToAsync(stream);
});

app.MapPost("/upload_many", async (IFormFileCollection myFiles) =>
{
    foreach (var file in myFiles)
    {
        var tempFile = Path.GetTempFileName();
        app.Logger.LogInformation(tempFile);
        using var stream = File.OpenWrite(tempFile);
        await file.CopyToAsync(stream);
    }
});

app.Run();

Comments

0
app.MapPost("/upload",
    async Task<IResult> (HttpRequest request) =>
    {
        if (!request.HasFormContentType)
            return Results.BadRequest();

        var form = await request.ReadFormAsync();

        if (form.Files.Any() == false)
            return Results.BadRequest("There are no files");

        var file = form.Files.FirstOrDefault();

        if (file is null || file.Length == 0)
            return Results.BadRequest("File cannot be empty");

        using var stream = file.OpenReadStream();

        var reader = new StreamReader(stream);
        var text = await reader.ReadToEndAsync();

        return Results.Ok(text);
    }).Accepts<IFormFile>("multipart/form-data"); 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.