NSwag icon indicating copy to clipboard operation
NSwag copied to clipboard

Cannot generate description for a file upload API

Open Pzixel opened this issue 4 years ago • 9 comments

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

Pzixel avatar Feb 04 '20 12:02 Pzixel