Description
Is there an existing issue for this?
- I have searched the existing issues
Is your feature request related to a problem? Please describe the problem.
When building a web API, authors occasionally need to support file uploads. There are two possible ways this can be achieved today:
- Process
HttpContext.Request.Body
directly - Use model binding with
IFormFile
orIFormFileCollection
This issue exists for traditional controller actions as well as Minimal APIs
Option 1 - Process Request Body Directly
This implementation will correctly upload a file; however, HttpRequest
has special binding semantics and will not modeled by the API Explorer, making it impossible to use with OpenAPI without additional work on the developer's part. Even if HttpRequest
was explored, it would be incorrect because the intent is to document the HTTP body and not the entire HTTP request.
app.MapPost(
"order/import",
async (
HttpRequest request,
[FromHeader( Name = "Content-Disposition" )] string contentDisposition,
CancellationToken cancellationToken) =>
{
if (!ContentDispositionHeaderValue.TryParse(contentDisposition, out var header) ||
!header.FileName.HasValue)
{
return Results.BadRequest();
}
var source = request.Body;
var path = Path.Combine( Path.GetTempPath(), "Quarantine", header.FileName.Value );
using var destination = new FileStream( path, FileMode.Create );
await source.CopyToAsync( destination, cancellationToken );
await destination.FlushAsync( cancellationToken );
destination.Seek(0L, SeekOrigin.Begin);
var id = await GetOrderId(id, cancellationToken);
var scheme = request.Scheme;
var host = request.Host;
var location = new Uri( $"{scheme}{Uri.SchemeDelimiter}{host}/order/{id}" );
return Results.Created( location, default );
})
.Accepts<Stream>( "application/pdf" )
.Produces( 201 );
Option 2 - Use IFormFile or IFormFileCollection
This is a common approach which uses multipart/form-data
as the media type to upload one or more files. As specified in RFC 7578, this approach is intended for HTML forms. When a HTML <form>
sends a POST
back to the server with its key/value pairs, it needs a way to include other content, such as files, in a distinctly separate way - e.g. multipart.
While this approach can work for a web API, it is unnecessary. A web API should not have to use HTML semantics to upload a file. Moreover, this approach requires clients to format request bodies in a particular way thus changing the wire protocol of the API.
The following is a completely valid file upload:
POST /message HTTP/2
Host: my.microblog.com
Content-Type: text/plain
Content-Length: 42
Content-Disposition: inline; filename="infomericial.txt"
I'm a Pepper! Wouldn't you like to be a Pepper too?
The following is also a valid file upload:
POST /message HTTP/2
Host: my.microblog.com
Content-Type: application/json
Content-Length: 42
Content-Disposition: inline; name="infomericial"
{"message": "I'm a Pepper! Wouldn't you like to be a Pepper too?"}
Describe the solution you'd like
Any HTTP request with a body can potentially be considered a file upload. "There can be only one" parameter bound to the HTTP request body.
For traditional, controller actions, an argument defined as [FromBody] Stream body
, where the name body
is irrelevant, should be handled by BodyModelBinderProvider
and BodyModelBinder
. The current implementation does not allow zero InputFormatter
instances, but it should. Binding to Stream
should be considered a special case and should be bound when the following are true:
ModelBinderProviderContext.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Body)
context.Metadata.ModelType.Equals(typeof(Stream))
InputFormatter
instances need not be considered. The onus of understanding and processing the content is on the developer who makes a conscience decision to use this setup. A developer can specify which file types are allowed by declaratively using [Consumes]
or imperatively asserting the content.
For Minimal APIs, a method parameter defined as Stream body
or [FromBody] Stream body
should be sufficient. A developer can specify which file types are allowed by declaratively using Accepts
or imperatively asserting the content.
Additional context
Related to #4868
There should also be better support multipart/*
; specifically for file uploads. This feature request has general applicability beyond file uploads so I'll track that as a separate issue.