Fixing Swagger in .NET 10
by G. Forrest
If you've upgraded an ASP.NET Core API to .NET 10 and your Swashbuckle security configuration stopped compiling, this is why — and why almost every answer you'll find online describes an API that no longer exists. It mostly relates to the security definitions.
The short version: Microsoft.OpenApi 2.0 changed how references are modeled, and it changed three things at once. The compiler reports them as five separate errors pointing at four different lines, which makes it hard to see that there's really only one change to understand.
The code that breaks
This compiled fine on .NET 8:
svcCollection.AddSwaggerGen(c =>
{
c.AddSecurityDefinition("apikey", new OpenApiSecurityScheme
{
Description = "Swagger API Security WH",
Type = SecuritySchemeType.ApiKey,
Name = "x-api-key",
In = ParameterLocation.Header,
Scheme = "apikey"
});
var scheme = new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "apikey"
},
In = ParameterLocation.Header
};
var requirement = new OpenApiSecurityRequirement
{
{ scheme, new List<string>() }
};
c.AddSecurityRequirement(requirement);
});
On .NET 10 it produces five errors:
CS0117 'OpenApiSecurityScheme' does not contain a definition for 'Reference'
CS0246 The type or namespace name 'OpenApiReference' could not be found
CS1950 The best overloaded Add method
'Dictionary<OpenApiSecuritySchemeReference, List<string>>.Add(
OpenApiSecuritySchemeReference, List<string>)'
for the collection initializer has some invalid arguments
CS1503 Argument 1: cannot convert from 'Microsoft.OpenApi.OpenApiSecurityScheme'
to 'Microsoft.OpenApi.OpenApiSecuritySchemeReference'
CS1503 Argument 2: cannot convert from 'Microsoft.OpenApi.OpenApiSecurityRequirement'
to 'System.Func<Microsoft.OpenApi.OpenApiDocument,
Microsoft.OpenApi.OpenApiSecurityRequirement>'
Read carefully, those five errors are telling you three distinct things.
1. References became types, not properties
CS0117 and CS0246.
In Microsoft.OpenApi 1.x, a reference was a property you hung on the object it referred to:
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "apikey"
}
In 2.0, a reference is a distinct type that stands in place of the object:
new OpenApiSecuritySchemeReference("apikey", document)
OpenApiSecurityScheme itself was not removed — you still use it for AddSecurityDefinition. What went away is its Reference property, along with the OpenApiReference class and the ReferenceType enum.
That distinction matters, because a lot of write-ups say "OpenApiSecurityScheme is deprecated," which sends people hunting for a replacement type that doesn't exist.
2. The requirement dictionary is keyed differently
CS1950 and the first CS1503.
The error text spells out the new shape:
Dictionary<OpenApiSecuritySchemeReference, List<string>>
OpenApiSecurityRequirement used to be keyed by OpenApiSecurityScheme. Now it's keyed by OpenApiSecuritySchemeReference — so even a correctly-constructed scheme object can't go in it.
This one is easy to misread, because the compiler points at your scheme variable rather than at the dictionary. It looks like a problem with how you built the scheme, when actually the container changed underneath it.
Note the value type too: List<string>, not IList<string> or string[]. Array.Empty<string>() will not compile here.
3. AddSecurityRequirement only takes a delegate now
The second CS1503, and the most consequential.
Argument 2: cannot convert from 'OpenApiSecurityRequirement'
to 'System.Func<OpenApiDocument, OpenApiSecurityRequirement>'
The overload that accepted a plain OpenApiSecurityRequirement is gone. The only signature takes a factory that receives the OpenApiDocument.
(It says "Argument 2" because AddSecurityRequirement is an extension method — argument 1 is the SwaggerGenOptions receiver.)
This isn't arbitrary. A reference needs to know which document it belongs to in order to resolve, and the delegate is how you get one.
The fix:
svcCollection.AddSwaggerGen(options =>
{
options.AddSecurityDefinition("apikey", new OpenApiSecurityScheme
{
Description = "Swagger API Security WH",
Type = SecuritySchemeType.ApiKey,
Name = "x-api-key",
In = ParameterLocation.Header
});
options.AddSecurityRequirement(document => new OpenApiSecurityRequirement
{
[new OpenApiSecuritySchemeReference("apikey", document)] = []
});
});
Everything collapses into one statement. The intermediate scheme and requirement variables have no equivalent in the new model — the reference is what goes in the dictionary.
[] is a collection expression that satisfies List<string>. new List<string>() works too.
Something else I noticed when implementing this fix was that passing document into the reference is not optional in spirit, only in syntax. This compiles:
options.AddSecurityRequirement(document => new OpenApiSecurityRequirement
{
[new OpenApiSecuritySchemeReference("apikey")] = [] // no document
});
and produces a document where the requirement is an empty object:
"security": [ {} ]
The Authorize padlock appears in Swagger UI, you paste in a key, and every request still returns 401. No exception, no warning, no log line. There's an open issue tracking this.
If your Authorize button does nothing, read the generated swagger.json before you read your code. "security": [{}] tells you the reference didn't resolve, and nothing else will. This problem I had seen many times before, and at first it looks like the "authorize" lock button works. It does not work out of the box and requires a bit more tweaking.
One line that looks related and isn't
Scheme = "apikey"
Scheme only applies to SecuritySchemeType.Http, where it holds "bearer" or "basic". On an ApiKey scheme it's ignored. Harmless — but it's the kind of line that makes you suspect the wrong thing while you're already confused.
The whole migration reduces to one sentence:
In 1.x a reference was a property on the thing. In 2.x it's a type that replaces the thing — and it needs to know which document it lives in.
Hold that, and the five compiler errors become one change with three consequences.
References
- Microsoft.OpenAPI 2.0.0.0 has breaking changes — dotnet/aspnetcore #61123
- OpenApiSecurityRequirement serializes as empty object when constructed programmatically — microsoft/OpenAPI.NET #2801
- Swagger AddSecurityRequirement fails after migrating from .NET 8 to .NET 10 — Microsoft Q&A
- Swashbuckle.AspNetCore #3757 — missing Microsoft.OpenApi.Models references
About
I'm Gavin, a full-stack developer working mainly in C# / ASP.NET Core / Azure and PHP/Laravel, with SQL, Postgres, Docker and AWS underneath.
I write about the problems that don't have a clean answer online - things I've run into and my debugging paths that actually worked. I hope you find them useful. Thanks for visiting.