From 9f0e7e9999c43b467e646a1a9d62a6ecd829fb21 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Tue, 8 Apr 2025 12:08:48 -0700 Subject: [PATCH 1/5] fix(openapi): handle optional enum formdata params in controller actions --- .../src/EndpointModelMetadata.cs | 3 +- .../OpenApiDocumentServiceTestsBase.cs | 5 +- ...OpenApiSchemaService.RequestBodySchemas.cs | 47 +++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/Mvc/Mvc.ApiExplorer/src/EndpointModelMetadata.cs b/src/Mvc/Mvc.ApiExplorer/src/EndpointModelMetadata.cs index 3fd46f1798e2..90560c52cc72 100644 --- a/src/Mvc/Mvc.ApiExplorer/src/EndpointModelMetadata.cs +++ b/src/Mvc/Mvc.ApiExplorer/src/EndpointModelMetadata.cs @@ -65,6 +65,7 @@ public static Type GetDisplayType(Type type) || underlyingType == typeof(TimeSpan) || underlyingType == typeof(decimal) || underlyingType == typeof(Guid) - || underlyingType == typeof(Uri) ? type : typeof(string); + || underlyingType == typeof(Uri) + || underlyingType.IsAssignableTo(typeof(Enum)) ? type : typeof(string); } } diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentServiceTestsBase.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentServiceTestsBase.cs index aa2246385b31..e0728f3ba05d 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentServiceTestsBase.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentServiceTestsBase.cs @@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.OpenApi; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Constraints; @@ -235,8 +236,10 @@ public ControllerActionDescriptor CreateActionDescriptor(string methodName = nul }; action.RouteValues.Add("controller", "Test"); action.RouteValues.Add("action", action.MethodInfo.Name); - action.ActionConstraints = [new HttpMethodActionConstraint(["GET"])]; action.EndpointMetadata = [..action.MethodInfo.GetCustomAttributes()]; + action.ActionConstraints = [new HttpMethodActionConstraint( + action.EndpointMetadata.OfType().SelectMany(a => a.HttpMethods).DefaultIfEmpty("GET") + )]; if (controllerType is not null) { foreach (var attribute in controllerType.GetCustomAttributes()) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.RequestBodySchemas.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.RequestBodySchemas.cs index 5eb02fb12744..5d44f87a1651 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.RequestBodySchemas.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.RequestBodySchemas.cs @@ -711,4 +711,51 @@ public class Parent public IDictionary SelfReferenceDictionary { get; set; } = new Dictionary(); } + /// + /// Regression test for https://github.com/dotnet/aspnetcore/issues/61327 + /// + [Fact] + public async Task RespectsEnumDefaultValueInControllerFormParameters() + { + // Arrange + var actionDescriptor = CreateActionDescriptor(nameof(TestBodyController.FormPostWithOptionalEnumParam), typeof(TestBodyController)); + + // Assert + await VerifyOpenApiDocument(actionDescriptor, VerifyOptionalEnum); + } + + [Fact] + public async Task RespectsEnumDefaultValueInMinimalApiFormParameters() + { + // Arrange + var builder = CreateBuilder(); + + // Act + builder.MapPost("/optionalEnum", ([FromForm(Name = "status")] Status status = Status.Approved) => { }); + + // Assert + await VerifyOpenApiDocument(builder, VerifyOptionalEnum); + } + + private void VerifyOptionalEnum(OpenApiDocument document) + { + var operation = document.Paths["/optionalEnum"].Operations[OperationType.Post]; + var properties = operation.RequestBody.Content["application/x-www-form-urlencoded"].Schema.Properties; + var property = properties["status"]; + + Assert.NotNull(property); + Assert.Equal(3, property.Enum.Count); + Assert.Equal("Approved", property.Default.GetValue()); + } + + [ApiController] + [Produces("application/json")] + public class TestBodyController + { + [Route("/optionalEnum")] + [HttpPost] + internal Status FormPostWithOptionalEnumParam( + [FromForm(Name = "status")] Status status = Status.Approved + ) => status; + } } From bbf21cd9a018ca72975b4b86abc8fc21b6ec321b Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Tue, 8 Apr 2025 12:29:51 -0700 Subject: [PATCH 2/5] use IsEnum --- src/Mvc/Mvc.ApiExplorer/src/EndpointModelMetadata.cs | 2 +- src/submodules/Node-Externals | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 160000 src/submodules/Node-Externals diff --git a/src/Mvc/Mvc.ApiExplorer/src/EndpointModelMetadata.cs b/src/Mvc/Mvc.ApiExplorer/src/EndpointModelMetadata.cs index 90560c52cc72..d893d3a04b01 100644 --- a/src/Mvc/Mvc.ApiExplorer/src/EndpointModelMetadata.cs +++ b/src/Mvc/Mvc.ApiExplorer/src/EndpointModelMetadata.cs @@ -66,6 +66,6 @@ public static Type GetDisplayType(Type type) || underlyingType == typeof(decimal) || underlyingType == typeof(Guid) || underlyingType == typeof(Uri) - || underlyingType.IsAssignableTo(typeof(Enum)) ? type : typeof(string); + || underlyingType.IsEnum ? type : typeof(string); } } diff --git a/src/submodules/Node-Externals b/src/submodules/Node-Externals new file mode 160000 index 000000000000..fb911deddbaf --- /dev/null +++ b/src/submodules/Node-Externals @@ -0,0 +1 @@ +Subproject commit fb911deddbaf7367146718374a403d393571f18a From f5c2c7baa91bb2b54016e2f82fd3dde12f6c3f36 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Tue, 8 Apr 2025 12:30:38 -0700 Subject: [PATCH 3/5] format --- .../Services/OpenApiDocumentServiceTestsBase.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentServiceTestsBase.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentServiceTestsBase.cs index e0728f3ba05d..a788ecb7d335 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentServiceTestsBase.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentServiceTestsBase.cs @@ -237,8 +237,11 @@ public ControllerActionDescriptor CreateActionDescriptor(string methodName = nul action.RouteValues.Add("controller", "Test"); action.RouteValues.Add("action", action.MethodInfo.Name); action.EndpointMetadata = [..action.MethodInfo.GetCustomAttributes()]; - action.ActionConstraints = [new HttpMethodActionConstraint( - action.EndpointMetadata.OfType().SelectMany(a => a.HttpMethods).DefaultIfEmpty("GET") + action.ActionConstraints = [new HttpMethodActionConstraint(action + .EndpointMetadata + .OfType() + .SelectMany(a => a.HttpMethods) + .DefaultIfEmpty("GET") )]; if (controllerType is not null) { From 6c42516a461a81ab0dd6b0c81459b9f6b4684639 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Tue, 8 Apr 2025 12:35:23 -0700 Subject: [PATCH 4/5] remove accidental submodule --- src/submodules/Node-Externals | 1 - 1 file changed, 1 deletion(-) delete mode 160000 src/submodules/Node-Externals diff --git a/src/submodules/Node-Externals b/src/submodules/Node-Externals deleted file mode 160000 index fb911deddbaf..000000000000 --- a/src/submodules/Node-Externals +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fb911deddbaf7367146718374a403d393571f18a From 0ecb0da73cf05df5f2514fb84ba623e0d2a88734 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Wed, 4 Jun 2025 11:49:21 -0700 Subject: [PATCH 5/5] fix error --- .../OpenApiSchemaService.RequestBodySchemas.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.RequestBodySchemas.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.RequestBodySchemas.cs index 5d44f87a1651..63a235b96ca3 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.RequestBodySchemas.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.RequestBodySchemas.cs @@ -739,7 +739,7 @@ public async Task RespectsEnumDefaultValueInMinimalApiFormParameters() private void VerifyOptionalEnum(OpenApiDocument document) { - var operation = document.Paths["/optionalEnum"].Operations[OperationType.Post]; + var operation = document.Paths["/optionalEnum"].Operations[HttpMethod.Post]; var properties = operation.RequestBody.Content["application/x-www-form-urlencoded"].Schema.Properties; var property = properties["status"];