Matěj Pokorný
Matěj Pokorný

Reputation: 17845

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.

Upvotes: 10

Views: 4734

Answers (2)

zhuber
zhuber

Reputation: 5524

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.

Upvotes: 4

Martin
Martin

Reputation: 21

You could also take a look into ISchemaFilter and the following Issue:

https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1148

This might help you to filter for your operation and add the diffrent contentStyles for the same type (IFormInput).

I believe that what you want to achieve is currently only possible with a custom attribute, however there is an active branch for enhanced FormsInput support in active development, maybe you can add a feature request.

https://github.com/domaindrivendev/Swashbuckle.AspNetCore/commits/enhance-support-for-forms

Upvotes: 0

Related Questions