Add support for multipart/data uploads in swagger

This commit is contained in:
2026-06-07 19:14:14 +02:00
parent 0ac29a3627
commit d5ca01e636

View File

@@ -5,6 +5,7 @@ using FrameProcessor.Middleware;
using FrameProcessor.Mqtt; using FrameProcessor.Mqtt;
using FrameProcessor.Storage; using FrameProcessor.Storage;
using FrameProcessor.UrlFetch; using FrameProcessor.UrlFetch;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.OpenApi; using Microsoft.OpenApi;
using Serilog; using Serilog;
@@ -50,6 +51,68 @@ builder.Services.AddOpenApi(options =>
]; ];
return Task.CompletedTask; return Task.CompletedTask;
}); });
// Microsoft.AspNetCore.OpenApi (10.0) does not emit a multipart/form-data
// request body for [FromForm] IFormFile parameters on MVC controllers, so
// Swagger UI renders no upload widget. Synthesize one here for any operation
// whose action takes form-bound parameters.
options.AddOperationTransformer((operation, context, _) =>
{
var formParams = context.Description.ParameterDescriptions
.Where(p => p.Source == BindingSource.FormFile || p.Source == BindingSource.Form)
.ToList();
if (formParams.Count == 0)
{
return Task.CompletedTask;
}
var properties = new Dictionary<string, IOpenApiSchema>();
var required = new HashSet<string>();
foreach (var p in formParams)
{
var isFile = p.Source == BindingSource.FormFile
|| typeof(IFormFile).IsAssignableFrom(p.ModelMetadata?.ModelType);
properties[p.Name] = new OpenApiSchema
{
Type = JsonSchemaType.String,
Format = isFile ? "binary" : null,
};
if (p.IsRequired)
{
required.Add(p.Name);
}
}
operation.RequestBody = new OpenApiRequestBody
{
Required = true,
Content = new Dictionary<string, OpenApiMediaType>
{
["multipart/form-data"] = new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Type = JsonSchemaType.Object,
Properties = properties,
Required = required,
},
},
},
};
if (operation.Parameters is { Count: > 0 })
{
var formNames = formParams.Select(p => p.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
var filtered = operation.Parameters.Where(p => p.Name is null || !formNames.Contains(p.Name)).ToList();
operation.Parameters = filtered.Count > 0 ? filtered : null;
}
return Task.CompletedTask;
});
}); });
builder.Services.AddOptions<MqttOptions>() builder.Services.AddOptions<MqttOptions>()