Skip to content

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

Closed
@JarrodOsborne

Description

@JarrodOsborne

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-mvcIncludes: MVC, Actions and Controllers, Localization, CORS, most templatesfeature-openapi

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions