diff --git a/src/OpenApi/src/Services/OpenApiDocumentService.cs b/src/OpenApi/src/Services/OpenApiDocumentService.cs index a2b070ae7981..cd09d8dceffb 100644 --- a/src/OpenApi/src/Services/OpenApiDocumentService.cs +++ b/src/OpenApi/src/Services/OpenApiDocumentService.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections; using System.Collections.Concurrent; using System.Collections.Frozen; using System.ComponentModel; @@ -665,7 +666,11 @@ private async Task GetFormRequestBody( } else { - if (isComplexType) + // Identify if this is a collection (excluding string) + var isCollection = typeof(IEnumerable).IsAssignableFrom(description.Type) && description.Type != typeof(string); + + // Only allow non-collection complex types (POCOs) to take over the root body + if (isComplexType && !isCollection) { complexTypeSchema = parameterSchema; } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.RequestBody.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.RequestBody.cs index 8a38a89952f4..3b683924b69f 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.RequestBody.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.RequestBody.cs @@ -1076,6 +1076,34 @@ await VerifyOpenApiDocument(action, document => }); } + [Fact] + public async Task GetOpenApiRequestBody_HandlesFromFormWithIEnumerable() + { + // Arrange + var builder = CreateBuilder(); + + // Act + builder.MapPost("/test", ([FromForm] IEnumerable values) => { }); + + // Assert + await VerifyOpenApiDocument(builder, document => + { + var paths = Assert.Single(document.Paths.Values); + var operation = paths.Operations[HttpMethod.Post]; + Assert.NotNull(operation.RequestBody); + var content = operation.RequestBody.Content; + Assert.Contains("multipart/form-data", content.Keys); + var formSchema = content["multipart/form-data"].Schema; + + Assert.Equal(JsonSchemaType.Object, formSchema.Type); + Assert.NotNull(formSchema.Properties); + Assert.Contains("values", formSchema.Properties); + var valuesProperty = formSchema.Properties["values"]; + Assert.Equal(JsonSchemaType.Array, valuesProperty.Type); + Assert.Equal(JsonSchemaType.Integer, valuesProperty.Items.Type); + }); + } + [Route("/form-mixed-types")] private void ActionWithMixedFormTypes([FromForm] Todo todo, IFormFile formFile, [FromForm] Guid guid) { }