-
Notifications
You must be signed in to change notification settings - Fork 714
Swagger UI broken imports "An API version is required, but was not specified." #1068
-
The API works fine when running in Brave browser via the Visual Studio debugger. But when I try to run it on Chrome or Edge locally and also on all browser when it is deployed on IIS remotely, I get Bad Request errors on swagger's generated files:
{"type":"https://docs.api-versioning.org/problems#unspecified","title":"Unspecified API version","status":400,"detail":"An API version is required, but was not specified."}
Here is our ApiVersioning setup (we use VB):
oBuilder.Services.AddApiVersioning(Sub(o)
o.ReportApiVersions = True
End Sub) _
.AddMvc() _
.AddOData(Sub(o)
o.AddRouteComponents("v{version:apiVersion}",
Sub(c)
c.AddSingleton(Of IODataDeserializerProvider,
cSnakeCaseEnumsODataDeserializerProvider)
c.AddSingleton(Of IODataSerializerProvider,
cUpperSnakeCaseEnumsODataSerializerProvider)
End Sub)
o.ModelBuilder.ModelBuilderFactory = Function() New cCamelCaseODataModelBuilder()
End Sub) _
.AddODataApiExplorer(Sub(o)
o.GroupNameFormat = "'v'VVV"
o.SubstituteApiVersionInUrl = True
End Sub)
oBuilder.Services.AddTransient(Of IConfigureOptions(Of SwaggerGenOptions), cConfigureSwaggerOptions)
oBuilder.Services.AddSwaggerGen(Sub(o)
o.AddSecurityDefinition("API Key Scheme", New OpenApiSecurityScheme() With {
.Type = SecuritySchemeType.Http,
.Scheme = "bearer",
.BearerFormat = "Api key",
.Description = "Api key authorization using the Bearer scheme."})
o.AddSecurityRequirement(New OpenApiSecurityRequirement From {
{New OpenApiSecurityScheme With {
.Reference = New OpenApiReference With {
.Type = ReferenceType.SecurityScheme,
.Id = "API Key Scheme"
}
},
{"readAccess", "writeAccess"}}
})
o.OperationFilter(Of cSwaggerDefaultValues)
End Sub)
And here is the app config:
oApp.UseSwagger()
oApp.UseSwaggerUI(Sub(o)
For Each oDescription In oApp.DescribeApiVersions()
Dim sURL = $"/swagger/{oDescription.GroupName}/swagger.json"
Dim sName = oDescription.GroupName.ToUpperInvariant()
o.SwaggerEndpoint(sURL, sName)
Next
End Sub)
You can see an live example of the error here:
https://openapi.3.bridgeitonline.co.uk/swagger/index.html
Any ideas?
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 6 comments 2 replies
-
Hmmm... API Versioning doesn't care about nor has any direct references to OpenAPI, Swagger UI, or Swashbuckle. My best guess, without more information, is that you have a route that maps over ~/swagger
or you have convention-based routing somewhere. In order to see this problem, the requested path would have to match something in the route table.
Another possibility is that the order of the middleware is incorrect. Assuming you are using controllers, UseSwagger()
and UseSwaggerUI
must come before MapControllers
. The fact that the behavior is different, in different environments, suggestions you have some type of conditional or branching logic in the configuration.
Beta Was this translation helpful? Give feedback.
All reactions
-
Thanks for your response. I've found that the issue doesn't actually behave differently in different environments, which makes a lot more sense. The reason it was working in Brave was because the swagger files were cached. After clearing cache it fails to load on Brave too.
My best guess, without more information, is that you have a route that maps over ~/swagger or you have convention-based routing somewhere. In order to see this problem, the requested path would have to match something in the route table.
I use convention-based routing for all of my OData controllers. Only the error controllers use attribute routing.
image
Another possibility is that the order of the middleware is incorrect. Assuming you are using controllers, UseSwagger() and UseSwaggerUI must come before MapControllers.
UseSwagger and UseSwaggerUI do come before MapControllers, so I'm going to have to try to narrow down the issue. Here's my app config:
Dim oApp = oBuilder.Build()
' Configure the HTTP request pipeline.
oApp.UseSerilogRequestLogging()
oApp.UseHttpsRedirection()
If oApp.Environment.IsDevelopment() Then
' Navigate to ~/$odata to determine whether any endpoints did not match an odata route template
oApp.UseODataRouteDebug
oApp.UseExceptionHandler("/error-development")
Else
oApp.UseExceptionHandler("/error")
End If
oApp.UseSwagger()
oApp.UseSwaggerUI(Sub(o)
For Each oDescription In oApp.DescribeApiVersions()
Dim sURL = $"/swagger/{oDescription.GroupName}/swagger.json"
Dim sName = oDescription.GroupName.ToUpperInvariant()
o.SwaggerEndpoint(sURL, sName)
Next
End Sub)
oApp.UseCors()
oApp.UseStaticFiles()
oApp.UseAuthentication()
oApp.UseAuthorization()
oApp.MapControllers()
oApp.MapHub(Of cDataHub)("/signalr/data")
oApp.MapHub(Of cVOIPHub)("/signalr/voip")
oApp.Run()
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
I've managed to track down the method causing the issue:
If I comment out: o.AddRouteComponents("v{version:apiVersion}")
, Swagger UI works (but I lose the API version in the URLs obviously), if I uncomment the line, Swagger UI imports die and I get the blank screen.
oBuilder.Services.AddApiVersioning(Sub(o)
o.ReportApiVersions = True
End Sub) _
.AddMvc() _
.AddOData(Sub(o)
'o.AddRouteComponents("v{version:apiVersion}") <<<<<<<<<<<<<<<<<<<<<< Works if commented out
o.ModelBuilder.ModelBuilderFactory = Function() New cCamelCaseODataModelBuilder()
End Sub) _
.AddODataApiExplorer(Sub(o)
o.GroupNameFormat = "'v'VVV"
o.SubstituteApiVersionInUrl = True
End Sub)
The above "works", but I lose url versioning:
image
oBuilder.Services.AddApiVersioning(Sub(o)
o.ReportApiVersions = True
End Sub) _
.AddMvc() _
.AddOData(Sub(o)
o.AddRouteComponents("v{version:apiVersion}") ' <<<<<<<<<<<<<<<<<<<<<<<<<<<< Breaks Swagger UI
o.ModelBuilder.ModelBuilderFactory = Function() New cCamelCaseODataModelBuilder()
End Sub) _
.AddODataApiExplorer(Sub(o)
o.GroupNameFormat = "'v'VVV"
o.SubstituteApiVersionInUrl = True
End Sub)
The above means Swagger UI is broken, but I now have working url versioning:
image
Beta Was this translation helpful? Give feedback.
All reactions
-
This issue only appears if the API version segment is the "root segment".
That is, o.AddRouteComponents("v{version:apiVersion}")
breaks Swagger UI,
but, o.AddRouteComponents("api/v{version:apiVersion}"
works.
We were planning on having the API accessed via a subdomain api.ourdomain.com
, so we didn't need api
to appear in the path itself.
Does this appear to be an unsupported configuration, or are we looking at a bug?
Beta Was this translation helpful? Give feedback.
All reactions
-
Strangely, if the swagger route has a multi-part prefix, then the API can have no prefix before the version:
That is:
o.AddRouteComponents("v{version:apiVersion}")
with a single-part swagger route prefix:
oApp.UseSwagger(Sub(o)
o.RouteTemplate = "docs/{documentName}/docs.json"
End Sub)
oApp.UseSwaggerUI(Sub(o)
o.RoutePrefix = "docs"
For Each oDescription In oApp.DescribeApiVersions()
Dim sURL = $"/docs/{oDescription.GroupName}/docs.json"
Dim sName = oDescription.GroupName.ToUpperInvariant()
o.SwaggerEndpoint(sURL, sName)
Next
End Sub)
breaks Swagger UI
But:
o.AddRouteComponents("v{version:apiVersion}")
with a multi-part swagger route prefix:
oApp.UseSwagger(Sub(o)
o.RouteTemplate = "docs/api/{documentName}/docs.json"
End Sub)
oApp.UseSwaggerUI(Sub(o)
o.RoutePrefix = "docs/api"
For Each oDescription In oApp.DescribeApiVersions()
Dim sURL = $"/docs/api/{oDescription.GroupName}/docs.json"
Dim sName = oDescription.GroupName.ToUpperInvariant()
o.SwaggerEndpoint(sURL, sName)
Next
End Sub)
works fine.
Beta Was this translation helpful? Give feedback.
All reactions
-
👀 1
-
This is indeed strange. There could be some edge case combination with Swashbuckle + OData + API Versioning. API Versioning, even at the routing level, only considers an Endpoint
that has the expected metadata applied. Other endpoints should be ignored. It's possible there is some wonkiness with the generated OData route templates and endpoints.
Is this example all current libraries and .NET? When I get a little time to carve out, I'll try to repro things.
Of course you could go with a RESTful API, not use a version in the URL segment, and it would just work. 😜 ...but I get it,. Some people really insist or want it that way. If you want to version by URL, it should work as expected. 😸
Beta Was this translation helpful? Give feedback.
All reactions
-
There could be some edge case combination with Swashbuckle + OData + API Versioning
I expected there was always going to be some conflicts found eventually 😅
Is this example all current libraries and .NET? When I get a little time to carve out, I'll try to repro things.
We're using:
.NET 7
(LTS)Asp.Versioning.Mvc 7.1.0
(Latest compatible is 7.1.1, just haven't updated)Asp.Versioning.Mvc.ApiExplorer 7.1.0
(Latest compatible)Asp.Versioning.OData 7.1.0
(Latest compatible)Asp.Versioning.OData.ApiExplorer 7.1.0
(Latest compatible)Microsoft.AspNetCore.OData 8.2.3-Nightly202312181316
(I'm using this because they implemented IEEE754Compatible in the accept header here) - Looks like 8.2.3 is stable now anyway (Latest 8.2.4)Swashbuckle.AspNetCore 6.2.3
(Latest 6.5.0) - Not sure why we're using this version, possibly just what was in the initial VS template.Swashbuckle.AspNetCore.Annotations 6.5.0
(Latest)
Of course you could go with a RESTful API, not use a version in the URL segment, and it would just work. 😜 ...but I get it,. Some people really insist or want it that way. If you want to version by URL, it should work as expected. 😸
Ha! We went back and forth over this for a while, but it seems that version in the URL is common, popular, easy to use and has great compatibility, so that's why we chose it 😁
I think that for us, we're going to switch away from the subdomain and have the API routes under api/v{version:apiVersion}
and the Swagger stuff under docs/api
, as this combination makes sense semantically for us and doesn't conflict.
Hopefully I've pointed you in the right direction for your debugging, if you get a chance to find the cause.
Thanks for the excellent library!
Beta Was this translation helpful? Give feedback.
All reactions
-
🚀 1
-
Thanks. This will be useful.
The URL method is quite popular, but it violates the Uniform Interface constraint. v1/order/123
and v2/order/123
aren't two different orders, they are two different representations. Aside from being a PITA to implement (surprisingly), the approach works for most cases. One of the more significant considerations is whether you intend to support HATEOAS and/or have independent service evolution (e.g. different APIs with varying versions). If you have symmetrical versioning, it will work fine, but it could be a maintenance nightmare. The URL method falls down when these aren't true; especially for public-facing APIs. If a client asks for v1/order/123
with a link to a customer and the API could be v1 or v2, which one should be picked? The truth is that the server cannot make this decision. It's up to the client. If the server assumes v1/customer/42
because that's the version for orders, that might not be what the client wants. Perhaps they onboarded to customers v2 already and that's all they use. The server can never know this without making assumptions or other bad coupling decisions. If the versions are symmetrical, the problem can be remedied, but it may also be a PITA to maintain. A sound versioning policy (say N-2 versions) can mitigate some the problem. The URL method is the only one that has this problem. Something to consider in the future. The GitHub API is a good example of a public API that versions by media type, which is the only method Fielding himself has said is actually RESTful. The URL path is the resource identifier so the query string is a pragmatic and mostly RESTful approach without violating any constraints.
Soapbox aside, I'll report back my findings. Thanks for reporting it.
Beta Was this translation helpful? Give feedback.