Nswag: SwaggerUi3: Execute button not working if parameter is IFormFile

Created on 8 Jun 2019  路  12Comments  路  Source: RicoSuter/NSwag

Hello,

when using an IFormFile parameter on an operation as a parameter (FileUpload) the "Execute" button in swagger UI3 is not working. I can see no error on the browser console. The Filepicker is working as expected but clicking on Execute makes no operation.
I am using AspCore 2.2., NSwag 12.3.1, OpenApi 3.0.
Controller:

[Route("test")]
[ApiController]
public class TestController : ControllerBase
{
    [HttpPost, Route("upload")]
    public IActionResult Upload([SwaggerFile]IFormFile file)
    {
        return Ok();
    }
}

Openapi spec

 "/test/upload": {
      "post": {
        "tags": [
          "Test"
        ],
        "operationId": "Test_Upload",
        "parameters": [
          {
            "type": "file",
            "name": "file",
            "in": "formData",
            "schema": {
              "type": "string",
              "format": "binary",
              "nullable": true
            },
            "nullable": true
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          }
        }
      }
    }
  },

Any ideas?

Thank you and best regards,
Peter

OpenAPI 3

Most helpful comment

For version 13.6 I found a new workaround. Using the [FromForm] attribute the generated spec just needs a small adjustment in post process.

public class FormDataModel {
   public IFormFile UploadFile {get; set; }
}

[HttpPost]
public IActionResult Upload([FromForm]FormDataModel form) {
   // Do something
}

The generated spec will be

/Upload
  post:
    operationId: Upload
    requestBody:
      content:
        multipart/form-data:
          schema:
            properties:
              UploadFile:
                type: string
                format: binary

The only thing missing is type: object directly underneath schema.
I don't know why the previous mentioned workaround doesn't work anymore, as it seems to also set the schema type to NJsonSchema.JsonObjectType.Object. But using below code at post process in combination with [FromForm] does produce a correct spec.

foreach (var operation in x.Operations)
{
    var content = operation.Operation.RequestBody?.Content;
    if (content != null && content.TryGetValue("multipart/form-data", out var schema) {
        schema.Schema.Type = NJsonSchema.JsonObjectType.Object;
    }
}

And this should produce:

/Upload
  post:
    operationId: Upload
    requestBody:
      content:
        multipart/form-data:
          schema:
            type: object        <- This was missing
            properties:
              UploadFile:
                type: string
                format: binary

All 12 comments

I'm having the same issue

Maybe a swagger ui 3 problem? Is the spec correct?

No the problem is the specification changed.

In OpenApi 2 this is the definition of a file upload:

/api/Upload/excel:
    post:
      tags:
        - Upload
      operationId: Upload_Import
      parameters:
        - type: file
          name: file
          in: formData
          schema:
            type: string
            format: binary
            nullable: true
          nullable: true
      responses:
        '200':
          description: ''
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary

but this is the definition in OpenApi 3:

  /api/Upload/excel:
    post:
      tags:
        - Upload
      operationId: Upload_Import
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
      responses:
        '200':
          description: ''
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary

This is what is being genereted right now:
s1

And this is what needs to be generated
s2

Is there any news on this issue? I've just encountered it after upgrading to NSwag v13, so I'd appreciate any workarounds馃槉

The UI can be fixed with the following post process:

foreach (var operation in x.Operations)
{
    var fileParameters = operation.Operation.Parameters.Where(p => p.Type == NJsonSchema.JsonObjectType.File).ToList();
    if (fileParameters.Any())
    {
        operation.Operation.RequestBody = new OpenApiRequestBody();
        var requestBodyContent = new OpenApiMediaType();
        requestBodyContent.Schema = new NJsonSchema.JsonSchema();
        requestBodyContent.Schema.Type = NJsonSchema.JsonObjectType.Object;
        foreach (var fileParameter in fileParameters)
        {
            requestBodyContent.Schema.Properties.Add(fileParameter.Name, new NJsonSchema.JsonSchemaProperty
            {
                Type = NJsonSchema.JsonObjectType.String,
                Format = "binary",
                Description = fileParameter.Description
            });
            operation.Operation.Parameters.Remove(fileParameter);
        }
        operation.Operation.RequestBody.Content.Add("multipart/form-data", requestBodyContent);
    }
}

However, this fails when C# code is generated. First, it replaces the named FileParameter with a Stream body argument. Also, the generated client doesn't send the content-type boundary, so it can not be parsed as an IFormFile in the backend.

Unbenannt

@RicoSuter, do you have any ideas?

@GeorgDangl did you switch from Swagger 2.0 to OpenAPI 3.0?

Yeah, I did initially. But for now, I switched back again to Swagger 2.0.

I can confirm the findings of @dbartumeu, file parameters can only be described in the request body in OpenAPI 3.0, no longer as parameters with type file.
Applying the post process transformation I described above solved the problem, but the C# generator then produced code that did not include the request boundary in the multipart body.

So all in all, I decided to switch back to Swagger 2.0. I've found it to be much more stable with other generators as well and I don't yet have the requirement to support OpenAPI 3.0. I've got an app that produces clients for C#, TypeScript, JavaScript, Java, PHP and Python and everything seems to work fine with Swagger 2.0 for now.

Can confirm, pretty annoying. Workaround for me is indeed using .UseSwaggerDocument() instead of .UseOpenApiDocument() .

edit: really can't use the workaround, as OAuth2 starts failing when I use Swagger2.0

Edit2: the workaround with custom post processor works

Seems like the new release 13.6.0 has broken something. I was able to get the file upload working with the workaround posted above by @GeorgDangl but after upgrading from 13.5.0 to 13.6.0 it no longer works. I decided to stay with the olde version for now.

Having said that I admit that I have no idea whether the file upload also works with code generated from the specification (of either version). There is another issue around this (https://github.com/RicoSuter/NSwag/issues/2673) and I feel it has not been resolved yet. It would be nice to get a proper solution on this soon - are there any plans on that @RicoSuter ?

For version 13.6 I found a new workaround. Using the [FromForm] attribute the generated spec just needs a small adjustment in post process.

public class FormDataModel {
   public IFormFile UploadFile {get; set; }
}

[HttpPost]
public IActionResult Upload([FromForm]FormDataModel form) {
   // Do something
}

The generated spec will be

/Upload
  post:
    operationId: Upload
    requestBody:
      content:
        multipart/form-data:
          schema:
            properties:
              UploadFile:
                type: string
                format: binary

The only thing missing is type: object directly underneath schema.
I don't know why the previous mentioned workaround doesn't work anymore, as it seems to also set the schema type to NJsonSchema.JsonObjectType.Object. But using below code at post process in combination with [FromForm] does produce a correct spec.

foreach (var operation in x.Operations)
{
    var content = operation.Operation.RequestBody?.Content;
    if (content != null && content.TryGetValue("multipart/form-data", out var schema) {
        schema.Schema.Type = NJsonSchema.JsonObjectType.Object;
    }
}

And this should produce:

/Upload
  post:
    operationId: Upload
    requestBody:
      content:
        multipart/form-data:
          schema:
            type: object        <- This was missing
            properties:
              UploadFile:
                type: string
                format: binary

Same issue for me with a simple [Fromform] object and binding variables to property. The workaround of @LennartKoot worked for me.

Was this page helpful?
0 / 5 - 0 ratings