dotnet / dotnet/aspnetcore

OpenAPI schema generation marks a required property as nullable when its declared type is an open generic type parameter

Open
#68,973 5 comments 0 reactions 0 assignees View on GitHub
area-minimal feature-openapi
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 5h
Merged PRs (30d)
276

Description

## Is there an existing issue for this?
- [x] I have searched the existing issues (no matching issue found)

## Describe the bug

When a type has a `required` property whose declared type is an open generic
type parameter (e.g. `T Item` on `Envelope`), the OpenAPI schema generated
by `Microsoft.AspNetCore.OpenApi` (`AddOpenApi()`) marks that property's
value schema as nullable (`oneOf: [null, ref]`), even when:

- The property is `required` (so `item` correctly appears in the schema's
top-level `required` array), and
- The concrete type argument (`Widget` below) is a non-nullable reference
type, in a project with `enable`.

A `required` reference-typed property with a concrete (non-generic) type
does **not** show this problem in the same generated document — only
properties whose type is an open generic parameter do. This suggests the
underlying `NullabilityInfoContext` resolution doesn't correctly propagate
the closed generic's type-argument nullability through to the open generic
parameter's `PropertyInfo`, so the OpenAPI schema generator falls back to
treating it as nullable.

We noticed this because it makes API consumers (e.g. someone building a
request by hand in Postman from the spec) unsure whether the field is
genuinely optional — it isn't; the underlying property is `required` and
non-nullable in C#, and `AddValidation()` rejects an explicit `null` value at
runtime. The schema is just misleading about it.

## Expected Behavior

`EnvelopeOfWidget.item`'s schema should be a plain `$ref` to `Widget`
(non-nullable), matching how `Widget.name` (a concrete, non-generic
`required string`) is correctly emitted as `{ "type": "string" }` with no
null branch, in the very same document.

## Steps To Reproduce

```csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
var app = builder.Build();

app.MapPost("/widgets", (Envelope request) => Results.Ok(request.Item));
app.MapOpenApi();

app.Run();

public class Envelope
{
public required T Item { get; init; }
}

public class Widget
{
public required string Name { get; set; }
}
```

`GET /openapi/v1.json` produces, among other schemas:

```json
"EnvelopeOfWidget": {
"required": ["item"],
"type": "object",
"properties": {
"item": {
"oneOf": [
{ "type": "null" },
{ "$ref": "#/components/schemas/Widget" }
]
}
}
}
```

## Exceptions (if any)

None — this is a schema-correctness issue, not a crash.

## .NET Version

```
10.0.302
```

## Anything else?

- ASP.NET Core / `Microsoft.AspNetCore.OpenApi` version: 10.0.11
- OS: Ubuntu 24.04 (linux-arm64), via a dev container
- `dotnet --info` (Host): Version 10.0.10

Happy to submit a PR for this if a maintainer can point me at the right spot
in the nullability/schema-generation code.

Contributor guide

Open the contributing guide

Research direction

Reproduce the issue with AddOpenApi(), MapOpenApi(), Envelope, and Widget, then inspect the Microsoft.AspNetCore.OpenApi nullability and schema-generation code, especially NullabilityInfoContext resolution for open generic parameters. Done means EnvelopeOfWidget.item is emitted as a non-nullable $ref to Widget without a null branch, while the property remains in the top-level required array.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, openapi
Domain
api, backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
70/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.