Nswag: Cannot generate description for a file upload API

Created on 4 Feb 2020  路  8Comments  路  Source: RicoSuter/NSwag

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

high bug

Most helpful comment

I see there is a problem and we should really try to fix this so that ppl do not need to use v2... hope to find time soon. Need to look into lots of file scenarios and add lots of tests.

All 8 comments

Any update on that?

Thanks, I will try it, although I don't think it will work in IFormFile case.

It will ignore all params and instead it will create a binary body parameter.

Got the same issue, any updates/workarounds? the attribute that @RicoSuter mentions didn't work :(.

I'm using IFormFile as an in parameter and got it to work when calling the function from a client. But it doesn't work from Swagger UI, but my real problem is that I'm using the generated swagger definition json file to update my Azure API Management instance. And that fails due to validation errors mentioned above...

Got my API to work by downgrading to Swagger2. I changed my Start up from:

services.AddOpenApiDocument(configure => { configure.Title = "B2B API"; });

To:

services.AddSwaggerDocument(configure => { configure.Title = "B2B API"; });

When using OpenApi3 I noted that the file is added under the parameters section but if I understand it right it should be under content -> multipart/form-data:
https://swagger.io/docs/specification/describing-request-body/file-upload/

I see there is a problem and we should really try to fix this so that ppl do not need to use v2... hope to find time soon. Need to look into lots of file scenarios and add lots of tests.

Seems to be related to https://github.com/RicoSuter/NSwag/issues/2220. It would be nice to get a proper solution on this soon - are there any plans on that @RicoSuter ?

Was this page helpful?
0 / 5 - 0 ratings