Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Specify content-type for files in multipart/form-data for Swagger

I have implemented endpoint with this signature

[HttpPost("Test")]
public IActionResult MyTest([Required] IFormFile pdf, [Required] IFormFile image)
{
    // some stuff...

    return Ok();
}

this generates following entry in swagger.json (the relevant part)

"content": {
    "multipart/form-data": {
        "schema": {
            "required": [
                "image",
                "pdf"
            ],
            "type": "object",
            "properties": {
                "pdf": {
                    "type": "string",
                    "format": "binary"
                },
                "image": {
                    "type": "string",
                    "format": "binary"
                }
            }
        },
        "encoding": {
            "pdf": {
                "style": "form"
            },
            "image": {
                "style": "form"
            }
        }
    }
}

but, I also need specify encoding, like in the specs (v3). So for my task, that JSON should look like this, I think...

"encoding": {
    "pdf": {
        "style": "form",
        "contentType": "application/pdf"
    },
    "image": {
        "style": "form",
        "contentType": "image/png, image/jpeg"
    }
}

But how can I do that from code? I thought about SwaggerParameter attribute, but it contains only description and required flag...

I'm using Swashbuckle.AspNetCore NuGeT package (version 5.0.0-rc2) on .NET Core 2.2.

like image 675
Matěj Pokorný Avatar asked Jun 17 '19 16:06

Matěj Pokorný


1 Answers

If you look at this line, you'll see that encoding is created with only Style property, while ContentType is not set. What you can do is set this manually by creating custom Attribute where you'd define your content type:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter,AllowMultiple = false)]
public class OpenApiEncodingContentTypeAttribute : Attribute
{
    public OpenApiEncodingContentTypeAttribute(string contentType)
    {
        ContentType = contentType;
    }

    public string ContentType { get; }
}

and then use that Attribute within IOperationFilter

public class FormContentTypeSchemaOperationFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        var contentTypeByParameterName = context.MethodInfo.GetParameters()
            .Where(p => p.IsDefined(typeof(OpenApiEncodingContentTypeAttribute), true))
            .ToDictionary(p => p.Name, s => s.GetCustomAttribute<OpenApiEncodingContentTypeAttribute>().ContentType);

        if (contentTypeByParameterName.Any())
        {
            foreach (var requestContent in operation.RequestBody.Content)
            {
                var encodings = requestContent.Value.Encoding;
                foreach (var encoding in encodings)
                {
                    if (contentTypeByParameterName.TryGetValue(encoding.Key, out string value))
                    {
                        encoding.Value.ContentType = value;
                    }
                }
            }
        }
    }
}

Then just decorate your parameters with this Attribute

[HttpPost("Test")]
public IActionResult MyTest([Required] [OpenApiEncodingContentType("application/pdf")] IFormFile pdf, [Required] [OpenApiEncodingContentType("image/png, image/jpeg")] IFormFile image)
{
    // some stuff...
    return Ok();
}

Also don't forget to define your IOperationFilter in AddSwaggerGen

services.AddSwaggerGen(opts =>
{
    // all other stuff
    opts.OperationFilter<FormContentTypeSchemaOperationFilter>();
})

This is what you get

"requestBody": {
  "content": {
    "multipart/form-data": {
      "schema": {
        "required": [
          "image",
          "pdf"
        ],
        "type": "object",
        "properties": {
          "pdf": {
            "type": "string",
            "format": "binary"
          },
          "image": {
            "type": "string",
            "format": "binary"
          }
        }
      },
      "encoding": {
        "pdf": {
          "contentType": "application/pdf",
          "style": "form"
        },
        "image": {
          "contentType": "image/png, image/jpeg",
          "style": "form"
        }
      }
    }
  }
}

You can probably improve IOperationFilter with additional checks/null-checks and other stuff that suits your needs, because this is just a basic implementation.

like image 154
zhuber Avatar answered Sep 21 '22 04:09

zhuber