NSwag
NSwag copied to clipboard
Cannot generate description for a file upload API
My current problem is I'm trying to implement a simple uploading API that could work from both my client and swaggerUI but I didn't succeed yet.
Consider following controller:
[ApiController]
[Route("[controller]/[action]")]
[Produces(MediaTypeNames.Application.Json)]
public class ConfirmController : ControllerBase
{
[HttpPost("{taskId}")]
[Consumes(MediaTypeNames.Image.Jpeg, MediaTypeNames.Application.Octet, "image/png")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task Photo(Guid taskId, [FromBody] Stream file)
{
}
}
This description generates an almost proper swagger description
"/Confirm/Photo/{taskId}": {
"post": {
"tags": [
"Confirm"
],
"operationId": "Confirm_Photo",
"parameters": [
{
"name": "taskId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "guid"
},
"x-position": 1
}
],
"requestBody": {
"x-name": "file",
"content": {
"application/json": {
"schema": {
"type": "string",
"format": "byte",
"nullable": false
}
}
},
"required": true,
"x-position": 2
},
"responses": {
"401": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MessageModel"
}
}
}
},
"200": {
"description": ""
},
"403": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MessageModel"
}
}
}
}
}
}
}
But it doesn't work: I'm getting 415 Unsupported Media Type
when I click Execute
button in swaggerUI. As a bonus Stream
argument is considered to be an application/json
, instead of what I specified in ConsumesAttribute
.
If I change [FromBody]
to [FromForm]
attribute then I'm able to call method without getting 415
error, but swaggerUI is either unable to perform request at all (see https://github.com/swagger-api/swagger-ui/issues/5821 ) or just sends empty body instead of file.
For example this method
[HttpPost("{taskId}")]
[Consumes("multipart/form-data")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task Photo(Guid taskId, [FromForm] IFormFile file)
{
}
Will produce following swagger.json:
{
"x-generator": "NSwag v13.2.2.0 (NJsonSchema v10.1.4.0 (Newtonsoft.Json v12.0.0.0))",
"openapi": "3.0.0",
"info": {
"title": "API",
"version": "v1"
},
"servers": [
{
"url": "http://localhost:55555"
}
],
"paths": {
"/Confirm/Photo/{taskId}": {
"post": {
"tags": [
"Confirm"
],
"operationId": "Confirm_Photo",
"parameters": [
{
"name": "taskId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "guid"
},
"x-position": 1
},
{
"name": "ContentType",
"in": "formData",
"schema": {
"type": "string",
"nullable": true
},
"x-position": 2
},
{
"name": "ContentDisposition",
"in": "formData",
"schema": {
"type": "string",
"nullable": true
},
"x-position": 3
},
{
"name": "Headers",
"in": "formData",
"schema": {
"nullable": true,
"oneOf": [
{
"$ref": "#/components/schemas/IHeaderDictionary"
}
]
},
"x-position": 4
},
{
"name": "Length",
"in": "formData",
"schema": {
"type": "integer",
"format": "int64"
},
"x-position": 5
},
{
"name": "Name",
"in": "formData",
"schema": {
"type": "string",
"nullable": true
},
"x-position": 6
},
{
"name": "FileName",
"in": "formData",
"schema": {
"type": "string",
"nullable": true
},
"x-position": 7
}
],
"responses": {
"401": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MessageModel"
}
}
}
},
"200": {
"description": ""
},
"403": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MessageModel"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"MessageModel": {
"type": "object",
"additionalProperties": false,
"properties": {
"Message": {
"type": "string",
"nullable": true
}
}
},
"IHeaderDictionary": {
"type": "object",
"x-abstract": true,
"additionalProperties": false,
"properties": {
"Item": {
"type": "array",
"items": {
"type": "string"
}
},
"ContentLength": {
"type": "integer",
"format": "int64",
"nullable": true
}
}
}
},
"securitySchemes": {
"Bearer": {
"type": "apiKey",
"description": "Type into the textbox: Bearer {your auth token}.",
"name": "Authorization",
"in": "header"
}
}
},
"security": [
{
"Bearer": []
}
]
}
Which is invalid as you can check at https://editor.swagger.io/ :
Structural error at paths./Confirm/Photo/{taskId}.post.parameters.1.in
should be equal to one of the allowed values
allowedValues: path, query, header, cookie
Jump to line 23
Structural error at paths./Confirm/Photo/{taskId}.post.parameters.2.in
should be equal to one of the allowed values
allowedValues: path, query, header, cookie
Jump to line 29
Structural error at paths./Confirm/Photo/{taskId}.post.parameters.3.in
should be equal to one of the allowed values
allowedValues: path, query, header, cookie
Jump to line 35
Structural error at paths./Confirm/Photo/{taskId}.post.parameters.4.in
should be equal to one of the allowed values
allowedValues: path, query, header, cookie
Jump to line 42
Structural error at paths./Confirm/Photo/{taskId}.post.parameters.5.in
should be equal to one of the allowed values
allowedValues: path, query, header, cookie
Jump to line 48
Structural error at paths./Confirm/Photo/{taskId}.post.parameters.6.in
should be equal to one of the allowed values
allowedValues: path, query, header, cookie
Jump to line 54
So my question is how do I describe a regular octet/stream
and/or formdata
to make it work in all cases?
All this applies to asp netcoreapp3.1