Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Swagger UI broken imports "An API version is required, but was not specified." #1068

DanielVernall started this conversation in General
Discussion options

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?

You must be logged in to vote

Replies: 6 comments · 2 replies

Comment options

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.

You must be logged in to vote
0 replies
Comment options

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()
You must be logged in to vote
0 replies
Comment options

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

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

image

You must be logged in to vote
0 replies
Comment options

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?

You must be logged in to vote
0 replies
Comment options

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.

You must be logged in to vote
1 reply
@commonsensesoftware
Comment options

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. 😸

Comment options

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!

You must be logged in to vote
1 reply
@commonsensesoftware
Comment options

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
2 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.