Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.NET 9 OpenAPI not working with nullable property with MaxLengthAttribute #59428

Open
1 task done
JarrodOsborne opened this issue Dec 11, 2024 · 3 comments
Open
1 task done
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-openapi

Comments

@JarrodOsborne
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

OpenAPI document generation fails when a request body class contains a nullable property with [MaxLength(...)].

Here is reproducible code:

using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddOpenApi();

var app = builder.Build();

app.MapControllers();
app.MapOpenApi();

await app.RunAsync();

[ApiController]
[Route("api/[controller]")]
public class MyController : ControllerBase
{
    public class MyRequestModel
    {
        // Both types fail with the same error
        [MaxLength(10)]
        public IList<string>? Items { get; set; }
        // public string[]? Items { get; set; }
    }

    [HttpPost]
    public IActionResult Post([FromBody] MyRequestModel request)
    {
        return Ok(request);
    }
}

Calling http://localhost:5000/openapi/v1.json results in:

     System.InvalidOperationException: The node must be of type 'JsonValue'.
         at System.Text.Json.Nodes.JsonNode.GetValue[T]()
         at Microsoft.AspNetCore.OpenApi.JsonNodeSchemaExtensions.ApplyValidationAttributes(JsonNode schema, IEnumerable`1 validationAttributes)
         at Microsoft.AspNetCore.OpenApi.OpenApiSchemaService.<>c__DisplayClass0_0.<.ctor>b__2(JsonSchemaExporterContext context, JsonNode schema)
         at System.Text.Json.Schema.JsonSchema.<ToJsonNode>g__CompleteSchema|104_0(JsonNode schema, <>c__DisplayClass104_0&)
         at System.Text.Json.Schema.JsonSchema.ToJsonNode(JsonSchemaExporterOptions options)
         at System.Text.Json.Schema.JsonSchema.ToJsonNode(JsonSchemaExporterOptions options)
         at System.Text.Json.Schema.JsonSchemaExporter.GetJsonSchemaAsNode(JsonTypeInfo typeInfo, JsonSchemaExporterOptions exporterOptions)
         at System.Text.Json.Schema.JsonSchemaExporter.GetJsonSchemaAsNode(JsonSerializerOptions options, Type type, JsonSchemaExporterOptions exporterOptions)        
         at Microsoft.AspNetCore.OpenApi.OpenApiSchemaService.CreateSchema(OpenApiSchemaKey key)
         at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
         at Microsoft.AspNetCore.OpenApi.OpenApiSchemaStore.GetOrAdd(OpenApiSchemaKey key, Func`2 valueFactory)
         at Microsoft.AspNetCore.OpenApi.OpenApiSchemaService.GetOrCreateSchemaAsync(Type type, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, ApiParameterDescription parameterDescription, Boolean captureSchemaByRef, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetJsonRequestBody(IList`1 supportedRequestFormats, ApiParameterDescription bodyParameter, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetRequestBodyAsync(ApiDescription description, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOperationAsync(ApiDescription description, HashSet`1 capturedTags, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOperationsAsync(IGrouping`2 descriptions, HashSet`1 capturedTags, IServiceProvider scopedServiceProvider, IOpenApiOperationTransformer[] operationTransformers, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOpenApiPathsAsync(HashSet`1 capturedTags, IServiceProvider scopedServiceProvider, IOpenApiOperationTransformer[] operationTransformers, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOpenApiDocumentAsync(IServiceProvider scopedServiceProvider, CancellationToken cancellationToken)   
         at Microsoft.AspNetCore.Builder.OpenApiEndpointRouteBuilderExtensions.<>c__DisplayClass0_0.<<MapOpenApi>b__0>d.MoveNext()

I have done some debugging and the fault happens here in JsonNodeSchemaExtensions.cs:101

            else if (attribute is MaxLengthAttribute maxLengthAttribute)
            {
                var targetKey = schema[OpenApiSchemaKeywords.TypeKeyword]?.GetValue<string>() == "array" ? OpenApiSchemaKeywords.MaxItemsKeyword : OpenApiSchemaKeywords.MaxLengthKeyword;
                schema[targetKey] = maxLengthAttribute.Length;
            }

The schema comes through as:
Image
But fails because it's a JsonArray and not a JsonValue

{
  "type" : [ "array", "null" ],
  "items" : {
    "type" : "string",
    "nullable" : false,
    "format" : null,
    "x-schema-id" : null,
    "pattern" : null
  },
  "nullable" : true
}

Please let me know if I'm doing something wrong, thanks!

Expected Behavior

The document generator should be able to handle a nullable array/collection with a MaxLength(N) validation attribute.
The resulting OpenAPI document should list the property as a nullable array, with maxItems as N

Steps To Reproduce

See bug description

Exceptions (if any)

See bug description for full trace

System.InvalidOperationException: The node must be of type 'JsonValue'.
         at System.Text.Json.Nodes.JsonNode.GetValue[T]()
         at Microsoft.AspNetCore.OpenApi.JsonNodeSchemaExtensions.ApplyValidationAttributes(JsonNode schema, IEnumerable`1 validationAttributes)

.NET Version

.NET 9

Anything else?

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-web-frameworks *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels label Dec 11, 2024
@martincostello martincostello added area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-openapi and removed area-web-frameworks *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels labels Dec 11, 2024
@DaleMckeown
Copy link

DaleMckeown commented Dec 12, 2024

I too am having this issue, but with MinLength instead of MaxLength. Has a workaround been found?

@mikekistler
Copy link
Contributor

Marking this as a bug.

@captainsafia
Copy link
Member

@JarrodOsborne Thanks for filing this issue and taking the time to investigate! Your analysis on the root cause of the issue is correct. Would you be interested in opening a PR to resolve this issue by updating the check in the ApplyValidationAttributes call?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-openapi
Projects
None yet
Development

No branches or pull requests

5 participants