Search code examples
c#asp.net-coreswaggermime-typesswagger-codegen

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.


Solution

  • 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.