Add DualInputDocumentFilter for Swagger content merging
Introduced the `DualInputDocumentFilter` class to merge Swagger operations with the same path but different `[Consumes]` attributes (`multipart/form-data` and `application/json`) into a single operation. This ensures both content types are visible in the Swagger UI. Updated `SwaggerConfiguration.cs` to: - Resolve conflicting actions by keeping the first variant. - Register the `DualInputDocumentFilter` to enable content type merging.
This commit is contained in:
125
DocumentOperator.API/Configuration/DualInputDocumentFilter.cs
Normal file
125
DocumentOperator.API/Configuration/DualInputDocumentFilter.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace DocumentOperator.API.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class DualInputDocumentFilter : IDocumentFilter
|
||||
{
|
||||
private readonly IApiDescriptionGroupCollectionProvider _apiDescriptionProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DualInputDocumentFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="apiDescriptionProvider">API description provider to access all endpoints</param>
|
||||
public DualInputDocumentFilter(IApiDescriptionGroupCollectionProvider apiDescriptionProvider)
|
||||
{
|
||||
_apiDescriptionProvider = apiDescriptionProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the filter to merge operations with different content types.
|
||||
/// </summary>
|
||||
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<OperationType>(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<string, OpenApiMediaType>()
|
||||
};
|
||||
}
|
||||
|
||||
// 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<string, OpenApiSchema>
|
||||
{
|
||||
["file"] = new OpenApiSchema
|
||||
{
|
||||
Type = "string",
|
||||
Format = "binary",
|
||||
Description = "PDF file to upload"
|
||||
}
|
||||
},
|
||||
Required = new HashSet<string> { "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<string, OpenApiSchema>
|
||||
{
|
||||
["base64Pdf"] = new OpenApiSchema
|
||||
{
|
||||
Type = "string",
|
||||
Format = "byte",
|
||||
Description = "Base64-encoded PDF file content"
|
||||
}
|
||||
},
|
||||
Required = new HashSet<string> { "base64Pdf" }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,13 @@ namespace DocumentOperator.API.Configuration
|
||||
Description = "PDF Verarbeitungs-Service für Validierung, Stempel, Zertifikate, Anhänge & Zusammenführung"
|
||||
});
|
||||
|
||||
// Resolve conflicting actions: Keep first variant
|
||||
// DualInputDocumentFilter will merge both variants into single operation
|
||||
options.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
|
||||
|
||||
// Add document filter to merge operations with different content types
|
||||
options.DocumentFilter<DualInputDocumentFilter>();
|
||||
|
||||
// XML-Kommentare einbinden
|
||||
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
|
||||
|
||||
Reference in New Issue
Block a user