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"
|
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
|
// XML-Kommentare einbinden
|
||||||
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||||
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
|
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
|
||||||
|
|||||||
Reference in New Issue
Block a user