AspNetCoreToSwaggerGenerator produces one "consumes" output when multiple are defined on the controller
#1,765 opened on Nov 22, 2018
Repository metrics
- Stars
- (6,291 stars)
- PR merge metrics
- (No merged PRs in 30d)
Description
I have a controller method that takes a Stream in the body and defines multiple ConsumesAttribute.ContentTypes:
public class FileController : Controller
{
[HttpPost("customer/{customerId}/file", Name = "UploadFile")]
[SwaggerResponse(StatusCodes.Status201Created, typeof(UploadFileResponse))]
[Consumes("application/octet-stream", new string[] { "application/pdf", "image/jpg", "image/jpeg", "image/png", "image/tiff", "image/tif" })]
public async Task<IActionResult> UploadFile([FromBody] Stream file, [FromRoute] string customerId, [FromQuery] FileQueryParameters queryParameters)
{
// file processing here
}
}
In my startup I'm suppressing Stream model binding, and I have a custom input formatter for a [FromBody] Stream parameter:
public class StreamInputFormatter : IInputFormatter
{
private readonly List<string> _allowedMimeTypes = new List<string>
{ "application/pdf", "image/jpg", "image/jpeg", "image/png", "image/tiff", "image/tif", "application/octet-stream" };
public bool CanRead(InputFormatterContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var contentType = context.HttpContext.Request.ContentType;
if (_allowedMimeTypes.Any(x => x.Contains(contentType)))
{
return true;
}
if (contentType == "application/octet-stream")
{
return true;
}
return false;
}
public Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var req = context.HttpContext.Request;
req.EnableRewind();
var memoryStream = new MemoryStream();
context.HttpContext.Request.Body.CopyTo(memoryStream);
req.Body.Seek(0, SeekOrigin.Begin);
return InputFormatterResult.SuccessAsync(memoryStream);
}
}
They are added the ServicesCollection:
services.AddMvc(options =>
{
options.InputFormatters.Add(new StreamInputFormatter());
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Stream)));
}
Swagger initialized as:
services.AddSwaggerDocument(document =>
{
document.DocumentName = "swagger";
document.DocumentProcessors.Add(new SecurityDefinitionAppender("Authorization", new SwaggerSecurityScheme
{
Type = SwaggerSecuritySchemeType.ApiKey,
Name = "Authorization",
In = SwaggerSecurityApiKeyLocation.Header,
Description = "Authorization"
}));
document.PostProcess = (d) =>
{
d.Info.Title = "File API";
d.Schemes.Clear();
d.Schemes.Add(SwaggerSchema.Http);
d.Schemes.Add(SwaggerSchema.Https);
};
});
app.UseSwagger(options =>
{
options.DocumentName = "swagger";
options.Path = "/swagger/v1/swagger.json";
});
app.UseSwaggerUi3(options =>
{
options.Path = "/swagger";
options.DocumentPath = "/swagger/v1/swagger.json";
});
The output of the swagger.json related to this method looks like so:
"post": {
"tags": [
"File"
],
"operationId": "File_UploadFile",
"consumes": [
"application/octet-stream" **<-- expecting more than one consumes**
],
"parameters": [
{
"name": "file",
"in": "body",
"required": true,
"schema": {
"type": "string",
"format": "byte",
"x-nullable": false
},
"x-nullable": false
},
{
"type": "string",
"name": "customerId",
"in": "path",
"required": true,
"x-nullable": false
},
{
"type": "string",
"name": "filename",
"in": "query",
"x-nullable": true
},
{
"type": "integer",
"name": "documentType",
"in": "query",
"x-schema": {
"$ref": "#/definitions/DocumentType"
},
"x-nullable": false,
"enum": [
0,
1,
2,
3
]
},
{
"type": "string",
"name": "versionNumber",
"in": "path",
"required": true,
"x-nullable": false
},
{
"type": "string",
"name": "tenantId",
"in": "path",
"required": true,
"x-nullable": false
}
],
"responses": {
"201": {
"x-nullable": true,
"description": "",
"schema": {
"$ref": "#/definitions/UploadFileResponse"
}
}
}
}
The expected output was:
"post": {
"tags": [
"File"
],
"operationId": "File_UploadFile",
"consumes": [
"application/octet-stream",
"application/pdf", "image/jpg",
"image/jpeg",
"image/png",
"image/tiff",
"image/tif"
],
"parameters": [
{
"name": "file",
"in": "body",
"required": true,
"schema": {
"type": "string",
"format": "byte",
"x-nullable": false
},
"x-nullable": false
},
{
"type": "string",
"name": "customerId",
"in": "path",
"required": true,
"x-nullable": false
},
{
"type": "string",
"name": "filename",
"in": "query",
"x-nullable": true
},
{
"type": "integer",
"name": "documentType",
"in": "query",
"x-schema": {
"$ref": "#/definitions/DocumentType"
},
"x-nullable": false,
"enum": [
0,
1,
2,
3
]
},
{
"type": "string",
"name": "versionNumber",
"in": "path",
"required": true,
"x-nullable": false
},
{
"type": "string",
"name": "tenantId",
"in": "path",
"required": true,
"x-nullable": false
}
],
"responses": {
"201": {
"x-nullable": true,
"description": "",
"schema": {
"$ref": "#/definitions/UploadFileResponse"
}
}
}
}
The generated client uses the StreamContent, but forces "application/octet-stream". There is no way to override it that I can see?
....
using (var request_ = new System.Net.Http.HttpRequestMessage())
{
var content_ = new System.Net.Http.StreamContent(file);
content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/octet-stream");
request_.Content = content_;
request_.Method = new System.Net.Http.HttpMethod("POST");
....
}
I would like to support multiple values for "consumes", for a variety of different file types, but only one consumes is outputted.
Is this possible somehow?