aspnet-api-versioning
aspnet-api-versioning copied to clipboard
Swagger not able to switch between versions
Hi,
I have been playing around with Minimal APIs in .net 6 using the pre-release version of ASP.Versioning
https://github.com/trouaux/minimal-api-playground In this repo I have a small sample project where I map a new API with two versions and two endpoints that should dynamically replace the version in the route.
Currently when I navigate to my swagger page, I'm not able to switch between versions ? am I doing something wrong ? (Requests to the different versions works fine)
ConfigureSwaggerOptions.cs & SwaggerDefaultValues.cs where taken from the examples folder of preview branches

Unfortunately, you've been hit by The Chicken or The Egg problem. 😆
I tried to hide it or get around it, but order matters in this case. The collated set of ApiVersionDescription is supposed be provided by IApiVersionDescriptionProvider via DI. WebApplication (e.g. app) implements IEndpointRouteBuilder and builds routes outside of the initial DI container setup, which signals a change for re-evaluation later in the routing system. That's all fine and well for routing, but it doesn't work with API Versioning's plus Swashbuckle's design. Despite what app.UseSwaggerUI appears to show, the callback is eagerly evaluated. This isn't too surprising since this registration happens after DI container construction.
This is the reason I had to add the DescribeApiVersions extension method instead of directly going through app.Services.GetRequiredService<IApiVersionDescriptionProvider>(). Under the hood, both methods do the same thing with the same implementation. The key difference is that WebApplication provides the endpoints you've registered as Minimal APIs, which aren't available via DI - yet. I haven't found a better way.
Long story short, app.UseSwaggerUI must come after all Minimal APIs are registered so they get picked up by DescribeApiVersions through the WebApplication. I would love to find a better way. I was afraid this could happen and you've already demonstrated it happening in preview. If you change the registration order, I expect things will just work.
The reason you get anything is because the API Explorer extensions will use
ApiVersioningOptions.DefaultApiVersionif it finds zero declared API versions (which is a valid scenario). You had Minimal APIs matching this API version so you got some results.
I think I'm experiencing a similar issue. However, I'm not using minimal APIs. I'm still using controllers and a startup.cs (because I need to use it in my SwaggerHostFactory).
In Startup.cs, I can cast my IApplicationBuilder to WebApplication and call DescribeApiVersions. However, I still have the issue with only one version showing up.
I've also tried using an implementation of IConfigureOptions<SwaggerUIOptions> and injecting the IApiVersionDescriptionProvider there. This was how I had it working before trying the latest pre-release.
@agilenut, I also am not using minimal APIs and had the same issue (though I only have program.cs and not a startup.cs). Moving app.UseSwaggerUI() to right before app.Run() as @commonsensesoftware explains, does fix the issue.
@Mhirji, thanks for the confirmation. If I move app.UseSwaggerUI to the last step in Startup.cs, it does work. However, that means that swagger UI is after app.UseAuthorization which means my fallback policy is applied to swagger UI which is not what I want. If I move authorization below app.UseSwaggerUI, then I also have to move app.UseEndpoints so that authorization applies to the endpoints. But then I'm back to this issue.
Great to hear @Mhirji.
@agilenut Hopefully that is the same situation you're in. No casts are required. What is returned from WebApplication.Create(args) or WebApplication.CreateBuilder(args).Build() are both WebApplication. WebApplication implements IEndpointRouteBuilder, which enables all of the MapXXX methods.
Unfortunately, I don't know that there will ever be a solid fix for this. Adding other DI services will not work. This is because when you start registering MapXXX methods, this occurs after IServiceCollection.BuildServiceProvider. This means that there is no way for an injected service or component to see or access information because it isn't registered yet. Curiously, IEndpointRouteBuilder doesn't actually build. It registers Endpoint configurations that are lazily evaluated at a later time. The only solution I've found thus far is to eagerly enumerate the endpoints from WebApplication, while also including any endpoints from DI.
If you're curious what happens, you can take a peek at DescribeApiVersions here. It still uses DefaultApiVersionDescriptionProvider, but it doesn't come directly from the container because there's no way to hook in the endpoints registered from the application. Since IEndpointRouteBuilder.ServiceProvider is available, everything else still comes from DI. The only significant drawback to this approach is that there is no way to swap out the implementation in the extension method. This is an unlikely scenario; especially, for Minimal APIs. If someone really does want to do it, all of the necessary pieces are available so that API versions can be described another way.
@agilenut to be clear, app.UseSwaggerUI does not have to be the last step, it just has to occur sometime after the endpoints are registered. Endpoint registration for Minimal APIs don't have any directly correlation or ordering to middleware, so you should register those first and then build your middleware pipeline, including app.UseSwaggerUI.
@commonsensesoftware , I think the issue is that app.UseAuthorization must happen before app.UseEndpoints. And, app.UseEndpoints must happen before app.UseSwaggerUI. By the transitive property, that means app.UseAuthorization must happen before app.UseSwaggerUI.
But this is what is causing my FallbackPolicy to apply to the swagger UI. So, the Swagger UI is now expecting a JWT Bearer token. This is why I need app.UseSwaggerUI to be before app.UseAuthorization to avoid this. But these things are now in conflict.
Also, the reason I had to do the cast is that DescribeApiVersions is off of WebApplication but the normal Startup.cs Configure method takes an IApplicationBuilder. I can change the signature of Configure and it works without the cast. That's just not the default signature in the older template. So, I didn't have it that way to begin with.
@agilenut I think we may be talking 🍎s and 🟠s here. The original question was about the setup for Minimal APIs, but it sounds like you are talking about the full setup with controllers.
- New Style - which uses
DescribeApiVersionsfor parity - Old Style - which uses
Startup.cs
Both methods are valid and supported. Controllers require MVC Core and go through the Application Model. Collating the API version information from them derives from DI without issue. In this type of setup, the order you wire up your middleware should not matter and is at your sole discretion.
True, the original question was for Minimal APIs. It just sounded to me like it might be because of the same underlying changes. If you'd like for me to open a separate issue, I can.
But, when I try the "Old Style", I still only get a single version.
Also, the way I had it working with the previous version is I used an implementation of IConfigureOptions<SwaggerUIOptions> to configure it. This kept my middleware setup clean and gave me a way to reuse the UI setup across multiple APIs.
Perhaps I can try to write a minimal reproduction solution.
Sounds like the issue might be related. Not so worried about a different issue, I just want to make sure we're on the same page. 😉
Looking back at the commit history, I think I realized that I did break something that I fixed, but it was after Preview 2 was published. 😞 Have a look here. This was a regression. The official fix for this will come in Preview 3. I'm hoping to get it out this weekend.
You have a few options, if you're champing at the bit:
- Fork the correct implementation (see:
mainordev/css/preview3-iteration1branches) and replace it in your DI setup. It should be pure Copy & Paste albeit with a different type name or namespace - Build directly off of
mainordev/css/preview3-iteration1 - Create local packages off of
mainordev/css/preview3-iteration1withdotnet pack --version-suffix alpha.3(or something)- You can put the package in a local folder and add it as a source to
nuget.config
- You can put the package in a local folder and add it as a source to
Thanks @commonsensesoftware. I've tested this with the latest version of DefaultApiVersionDescriptionProvider and I can confirm that it is now working! It works with both the "Old Style" in the examples and with an implementation of IConfigureOptions<SwaggerUIOptions>. Looking forward to Release 3. You're doing amazing work!
@commonsensesoftware moving app.UseSwaggerUI() below the mapping of Minimal APIs did indeed fix the issue, thanks a lot for the help and the discussion it created 😃
FYI, Preview 3 is now available. The ordering issue is unsolved (and probably won't ever be), but the regression issue should be fixed for "Old Style" configurations. Let me know if you run into any additional issues.
I can confirm Preview 3 has fixed the issue I had related to this thread.
FYI 6.0 has been officially released and includes all of this functionality as well as any fixes. It looks like the original issue has been resolved and all the questions answered. Thanks for all the discussion and feedback. If I've somehow missed something, I'm happy to reopen the issue. Thanks again.
Our team has moved to the new version. Working well so far!