From 386a124a4e8f6cfb9b65b64e8f1b467357cd04a9 Mon Sep 17 00:00:00 2001 From: TekH Date: Mon, 13 Jul 2026 16:04:55 +0200 Subject: [PATCH] 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. --- .../Configuration/DualInputDocumentFilter.cs | 125 ++++++++++++++++++ .../Configuration/SwaggerConfiguration.cs | 7 + 2 files changed, 132 insertions(+) create mode 100644 DocumentOperator.API/Configuration/DualInputDocumentFilter.cs diff --git a/DocumentOperator.API/Configuration/DualInputDocumentFilter.cs b/DocumentOperator.API/Configuration/DualInputDocumentFilter.cs new file mode 100644 index 0000000..7f9a42b --- /dev/null +++ b/DocumentOperator.API/Configuration/DualInputDocumentFilter.cs @@ -0,0 +1,125 @@ +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace DocumentOperator.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" } + } + }); + } + } + } + } + } +} diff --git a/DocumentOperator.API/Configuration/SwaggerConfiguration.cs b/DocumentOperator.API/Configuration/SwaggerConfiguration.cs index 431ac04..d280ff7 100644 --- a/DocumentOperator.API/Configuration/SwaggerConfiguration.cs +++ b/DocumentOperator.API/Configuration/SwaggerConfiguration.cs @@ -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(); + // XML-Kommentare einbinden var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);