using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace DocumentService.API.Configuration
{
///
/// Swagger document filter that merges operations with same path but different [Consumes] attributes.
/// Ensures both multipart/form-data and application/json variants are visible in Swagger UI.
///
public class DualInputDocumentFilter : IDocumentFilter
{
private readonly IApiDescriptionGroupCollectionProvider _apiDescriptionProvider;
///
/// Initializes a new instance of the class.
///
/// API description provider to access all endpoints
public DualInputDocumentFilter(IApiDescriptionGroupCollectionProvider apiDescriptionProvider)
{
_apiDescriptionProvider = apiDescriptionProvider;
}
///
/// Applies the filter to merge operations with different content types.
///
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
var allApiDescriptions = _apiDescriptionProvider.ApiDescriptionGroups.Items
.SelectMany(g => g.Items)
.ToList();
// Group by path
var groupedByPath = allApiDescriptions
.GroupBy(x => "/" + x.RelativePath)
.ToList();
foreach (var group in groupedByPath)
{
var path = group.Key;
if (!swaggerDoc.Paths.ContainsKey(path))
continue;
var pathItem = swaggerDoc.Paths[path];
// Find multipart and JSON variants
var multipartDesc = group.FirstOrDefault(x =>
x.SupportedRequestFormats.Any(f => f.MediaType == "multipart/form-data"));
var jsonDesc = group.FirstOrDefault(x =>
x.SupportedRequestFormats.Any(f => f.MediaType == "application/json"));
// If we have both variants, merge them into single operation
if (multipartDesc != null && jsonDesc != null)
{
var httpMethod = multipartDesc.HttpMethod?.ToLowerInvariant();
OperationType operationType;
if (!Enum.TryParse(httpMethod, true, out operationType))
continue;
if (!pathItem.Operations.ContainsKey(operationType))
continue;
var operation = pathItem.Operations[operationType];
// Ensure RequestBody exists
if (operation.RequestBody == null)
{
operation.RequestBody = new OpenApiRequestBody
{
Required = true,
Content = new Dictionary()
};
}
// Add multipart/form-data if missing
if (!operation.RequestBody.Content.ContainsKey("multipart/form-data"))
{
operation.RequestBody.Content.Add("multipart/form-data", new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Type = "object",
Properties = new Dictionary
{
["file"] = new OpenApiSchema
{
Type = "string",
Format = "binary",
Description = "PDF file to upload"
}
},
Required = new HashSet { "file" }
}
});
}
// Add application/json if missing
if (!operation.RequestBody.Content.ContainsKey("application/json"))
{
operation.RequestBody.Content.Add("application/json", new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Type = "object",
Properties = new Dictionary
{
["base64Pdf"] = new OpenApiSchema
{
Type = "string",
Format = "byte",
Description = "Base64-encoded PDF file content"
}
},
Required = new HashSet { "base64Pdf" }
}
});
}
}
}
}
}
}