-
Notifications
You must be signed in to change notification settings - Fork 704
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Controllers marked [ApiVersionNeutral] do not appear in Swagger #118
Comments
Never mind, I think I have worked this out - I need to amend the way my RoutePrefix works, as I currently have the same prefix across different controllers which is a mistake. I will close this issue as I believe this to be user error - sorry! |
You didn't specify if this was ASP.NET Web API or Core. I did a sanity check and it appears that the Web API version does have a bug that doesn't honor API version neutrality. One thing that can be confusing about API versioning is that once you enable it, everything has an API version. There is really no such thing as no API version. Despite my effort to use a clear name, I failed. API version-neutral does not mean unversioned. Instead, it means every version (e.g. neutral or I don't care which version). This should mean that a version-neutral API should appear for every known version. |
I was just about to re-open, as what I thought I had done wrong hasn't in fact fixed the issue! To clarify what I'm trying to achieve: I have a controller called AuditController, which now has a V2 method. I then considered that, rather than having to append another ApiVersion attribute every time a new version of some other controller is released, if nothing has changed in EntityTypesController then I could simply use [ApiVersionNeutral] - and keep using this until the point at which I need to version the EntityTypesController. It works fine when I tested this from my test client consuming the API, but I couldn't then see the controller in the Swagger documents - which may be the bug you talk about above. Hope that adds some clarification! |
Just as a further explanation - I'm trying to avoid my clients needing to pass different versions depending on what service call they are making. This is a gateway api, and I essentially want the client to be able to just specify what "version of the API" they are using once, for every request. In other words, if a client says they are on version 2 and wants audits, they'll get the V2 audit object. But if they then request EntityTypes, event though they will still pass api-version 2 in the request, they will just get the "VersionNeutral" entity types controller - until such point that I have to amend the EntityTypesController, in which case I will remove the ApiVersionNeutral attribute and replace it with specific ApiVersion attributes instead. I hope I'm making sense(!) |
For documentation, it's definitely an issue. It was a simple oversight on my part. I'm in the process of working on a fix. In terms of your approach to address your versioning scenarios, this is a plausible solution. Kind in mind, however, that by going version-neutral the controller will accept any version, even ones that that you don't necessarily want to support. This might be fine now, but it could come back to haunt you. I had that happen with one team. They went from being version-neutral to versioned, which one would think should be ok if the client is passing a version. In that case, the clients were, in fact, passing an API version, but it wasn't a supported API version. It was some arbitrary API version that was being accepted and ultimately broke once a specific API version was required. Be careful with being version-neutral if you think you might ever become versioned - there be dragons. There are some other alternatives to achieving your goal:
The IApiVersionSelector was specifically meant to help address the scenario you are describing. The single method SelectVersion accepts the current HttpRequestMessage and an aggregation of all discovered API versions (so you don't have to do the hard work). You can then choose the best API version given the API version model or some other information from the current request. In your case, it would be the request itself for one specific route. The IApiVersionSelector.SelectVersion method is only called when no API version is specified and implicitly API versioning is allowed. This approach can be taken to the extreme, which would be to create a version mapping between clients and implemented API versions. Once a client calls for the first time, they are bound to the API version they matched to. From that point forward, they will always get that version. You'd have to write some bits to store this mapping and you'd want to pre-load it some where in the pipeline, but it is possible. If you have external storage holding this mapping, you'll definitely want to do it outside the IApiVersionSelector because it's design is [intentionally] not asynchronous. If this is something you are interested in pursuing, you can find more information in the API Version Selector topic. Their implementations are pretty simple. Feel free to checkout the source for how to implement your own. I hope that helps. |
Wow that is super helpful, thank you - I will look further into the IApiVersionSelector as you suggest. This is a fairly new api and not yet fully out in the wild, so it will be good to get this right up front. Thanks again! |
Hi guys, Can I find any example on how to expose an ApiVersionNeutral Controller in Swagger? Thank you |
An API version-neutral controller will appear in every Swagger document (which is per API version). I'm not sure if there is an example for vanilla MVC, but there is an example for OData. You can see the Web API example here, but there's a similar equivalent for Core too. There is a curious case of all your controllers being version-neutral (which sounds like a really, really bad idea). In this case, you should only have a single API version, which would be defined by If this is somehow not the case for you, but you have repro you can share (best) or at least your setup, then some better guidance can be provided. |
Hi Chris, Thank you so much for your fast reply. [ApiVersionNeutral] Thanks for the help! |
Does accessing your API even work? I would not expect it to with this configuration. A controller cannot be simultaneously versioned and version-neutral. This would be ambiguous and should result in a 500. You should now be able to version down to the action level. This would not be ambiguous because the action level versioning is explicit and trumps whatever is defined at the controller level. The most obvious case for this would be I would start with those modifications and I'd also verify you can even call the API (via Postman or something), if you haven't already. If things still aren't working, then it's possible there is an issue with this particular setup that hasn't been caught before - the test matrix of combinations is quite large. Versioning, and hence API exploration, down to the action level is supposed be supported, even in Web API. Let me know. |
Sorry I didn't explain myself right. I wanted to say that I use those attributes but in different controllers. Detected the problem now, I had a typo in the controller suffix name (blindly I didn't detect it) and that's why it was not appearing in swagger, it had nothing to do with ApiVersionNeutral. |
Ah … awesome. Glad you got it working. By separate section, do you mean place all version-neutral APIs in a single document? If yes, that is technically possible, but not supported out-of-the-box. You'd have to extend the VersionedApiExplorer and recollate the results before the Swagger generator uses the discovered action descriptors or provide a Swagger generator extension/customization that recollates the results from the IApiExplorer before generating documents. In Web API and Swashbuckle, you might be able to achieve this via the Remember that a version-neutral API accepts any and all API versions, including none at all. Since the Swagger documents are generated according to API version, there isn't a nice way to have these tallied together. Furthermore, Swagger/OpenAPI documents generated this way are meant to describe the API surface area by API version. A consumer is looking at all the APIs in a particular version. A version-neutral API should, therefore, appear in every document because it applies to every API version. While it should be possible to bucketize things the way you want, do keep in mind that a client will have to stitch the set of versioned and version-neutral APIs together. The default behavior collates all applicable APIs together per API version. Also keep in mind that a version-neutral API is orthogonal to a client - e.g. it's more of an implementation detail. Unless you allow the client to not specify an API version (which you should never allow), they don't know that behind the scenes their request is going to the same implementation; regardless of the API version they specify. Finally, when it comes to a Swagger/OpenAPI document, a version-neutral API still requires an API version by default. By putting things into their own bucket, you are inviting clients to specify bogus API versions that would likely otherwise be avoided. This could lead to problems down the road. Keep in mind that when a client sees a version-neutral API in the Swagger document, they don't know that it's version-neutral; they just know it appears in every document and API version. I hope that helps. |
Thank you so much for your time, very helpful explanation. |
Sorry to bump an old thread. Is there any additional setup required to get controllers marked as |
@nathanjwtx do you have any controllers with a specific API version? A version-neutral API should appear in every group by API version. The API Explorer extensions, literally just add a copy to every group. However, if there are zero, specific API versions, then there is no group to put them in! That is an edge that has no real defense because it's a non sequitur in the context of API versioning. It should only be a problem if every API is version-neutral, but then you have to ask why you have versioning in the first place then? A version-neutral API accepts any and all API versions, including none at all. This is largely an implementation detail and one that cannot be easily conveyed in OpenAPI. Version-neutral is not the same thing as no API version (though it's kind of easy to think about it that way). You shouldn't want to have APIs specifically show up as version-neutral in the OpenAPI UI, but you could make it work with some effort on your part. The default behavior simply adds version-neutral APIs to every known API version. A client has no idea that it's actually version-neutral. The other benefit of that is that it helps clients avoid calling your API with invalid API versions against a version-neutral API (which I've seen in the wild). I'm not sure if that is your current state of affairs, but that is the only scenario I know of where this can happen. If that is your scenario, you should see that adding any API with a specific API version will make it appear in the API Explorer. If that isn't your case, then you'll have to share some additional details. |
Yes, at the moment all of our routes are either v1 or v2. Maybe a bit about the problem I'm trying to overcome. We have a couple of routes that aren't changing with the new version so I tried adding |
If you can provide a sample, I can provide more concrete guidance. I'm getting the impression that you are trying to minimize touch points between versions or you are bolting them on formally with API Versioning. Admirable goals, but punting to |
We have a status route that won't be changing across versions as all it does it query the status of internal APIs that feed the public API. Here is a stripped down version of the controller. With the
|
@nathanjwtx You cannot apply The question is whether you have /// <summary>
/// Provides status information.
/// </summary>
[ApiController]
[ApiVersionNeutral]
[Route( "[controller]" )]
public class StatusController : ControllerBase
{
/// <summary>
/// Returns API status information.
/// </summary>
/// <returns>The current API status.</returns>
[HttpGet]
public IActionResult Get() => Ok();
} Without going through every supported version, you can see that version-neutral will show up in each defined version. If there are zero explicit versions defined, then there is nowhere for them to show up. Comparing against the sample project might help you determine what's different in your own project.
|
We have Would this have something to do with how we setup our versioning? We went with adding the v2 routes in the same controller as the v1, but with a new name, e.g. |
Hmm... names such as I whipped up an example that should repro your scenario: /// <summary>
/// Represents speech.
/// </summary>
/// <param name="Message">The spoken message.</param>
public record Speech( string Message );
/// <summary>
/// Manages spoken words.
/// </summary>
[ApiController]
[ApiVersion( "1.0" )]
[ApiVersion( "2.0" )]
[Route( "[controller]" )]
public class SpeechController : ControllerBase
{
/// <summary>
/// Gets a default spoken message.
/// </summary>
/// <returns>A spoken message.</returns>
[HttpGet]
[MapToApiVersion( "1.0" )]
public IActionResult Get() => Ok( new Speech( "Hello world!" ) );
/// <summary>
/// Gets a default spoken message.
/// </summary>
/// <param name="version">The requested API version.</param>
/// <returns>A spoken message.</returns>
[HttpGet]
[MapToApiVersion( "2.0" )]
public IActionResult GetV2( ApiVersion version ) =>
Ok( new Speech( $"Hello world! ({version})" ) );
/// <summary>
/// Echoes the spoken speech.
/// </summary>
/// <param name="speech">A spoken message.</param>
/// <returns>The original, spoken message.</returns>
[HttpPost]
[MapToApiVersion( "2.0" )]
public IActionResult Echo( [FromBody] Speech speech ) => Ok( speech );
} Which produces what I would expect for Attached is the ever-so-slightly changed Swashbuckle Sample which illustrates everything all put together. Maybe that will help you figure out what's different in your application. |
I haven't had time to fully try this out yet but to clarify, you can't combine |
@nathanjwtx to clarify, you cannot have a route that is both versioned and version-neutral; regardless of whether it is the same class. The result is ambiguous. Declaring an API version-neutral is an all or nothing definition. Mapping matched versions to one place and all unmatched versions to another place is unsupported. It is effectively the same problem as combining If you really want a version-neutral endpoint, but with version-specific logic, this is possible, but it's not afforded by the routing system. In such a scenario, your API is version-neutral and your action can consume the |
Sorry, I didn't explain what I meant very well. In my class can I have the following (pseudo code)? Or would the
|
@nathanjwtx yes - that is a supported scenario. What that means is that the specific, corresponding action is API version-neutral forevermore. You just need to make sure that's what you want. 😉 For example, I can add this additional action the /// <summary>
/// Deletes a spoken message.
/// </summary>
/// <returns>None.</returns>
[HttpDelete]
[ApiVersionNeutral]
public IActionResult Delete() => NoContent(); Now, |
Hmm, I'm stumped. I can make requests to the version neutral route but I can't get it to appear in the generated docs. I looked through the sample project you attached but I couldn't see anything in the Swagger setup that we don't have. As a hacky workaround I've written a v2 route that calls the v1 route. Not ideal but at least now it appears in the docs. |
I have integrated Swagger to my versioned API successfully and have been playing with creating different versions of controllers. I wanted to mark a particular controller as ApiVersionNeutral, which works fine from my test client, but the controller then disappears from Swagger - understandable, as I can request the version in Swagger and so it makes sense that something without a version doesn't appear.
So, my question is - is there any way to make neutral controllers appear regardless of what version has been requested through Swagger?
The text was updated successfully, but these errors were encountered: