I'm curious if there has been any attempts at connecting controllers that use this versioning scheme with Swagger/Swashbuckle? I thought I'd ask here since I can see this being used often in conjunction with those kinds of libraries.
I think I've got the _version resolution_ sorted. Does this logic look correct for determining which actions to include per API version? or is there a better way....
static bool ResolveVersionSupportByApiVersionAttributes(ApiDescription apiDescription, string targetApiVersion)
{
if (apiDescription.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<ApiVersionNeutralAttribute>(true).Any())
return true; // always include neutrally marked controllers
var controllerVersionAttributes = apiDescription.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<ApiVersionAttribute>(true);
if (!controllerVersionAttributes.Any())
return true; // include when no attributes are defined
if (targetApiVersion.StartsWith("v"))
targetApiVersion = targetApiVersion.Substring(1); // remove the leading "v" in `v{x.x}`
var apiVersion = ApiVersion.Parse(targetApiVersion);
var controllerApiVersion = controllerVersionAttributes
.Where(x => x.Versions.Contains(apiVersion))
.FirstOrDefault();
// has a compatible version, now check the action for [MapToApiVersion]
if (controllerApiVersion != null)
{
var actionMapToAttributes = apiDescription.ActionDescriptor.GetCustomAttributes<MapToApiVersionAttribute>(true);
if (!actionMapToAttributes.Any())
return true; // no MapTo attributes matched, then include the action
if (actionMapToAttributes.Any(x => x.Versions.Contains(apiVersion)))
return true; // include mapped action
}
return false;
}
but I wanted to get a list of versions discovered in the assembly, rather than manually defining them. Should I also loop over all the ControllerDescriptors here as well, or is there a "discovered versions" registry somewhere (I couldn't find one.)
GlobalConfiguration.Configuration
.EnableSwagger(c => {
c.MultipleApiVersions(
ResolveVersionSupportByApiVersionAttributes,
(versionInfoBuilder) => {
// unsure how to get all the supported versions that were discovered here
versionInfoBuilder.Version("2.0", "Swashbuckle Dummy API v2.0");
versionInfoBuilder.Version("1.0", "Swashbuckle Dummy API v1.0");
versionInfoBuilder.Version("1.0-alpha", "Swashbuckle Dummy API v1.0-alpha");
}
);
c.CustomProvider(defaultProvider => new ODataSwaggerProvider(defaultProvider, c)
.Configure(odata => odata.IncludeNavigationProperties())
);
})
.EnableSwaggerUi(c => {
c.EnableDiscoveryUrlSelector();
});
Appreciate your thoughts here :)
PS: I haven't quite got a working version with config.MapVersionedODataRoutes("odata", "odata", models);, but it works happily if I use the built in config.MapODataServiceRoute("odata", "odata", models.First()) and pass in the first model only.
There have definitely been some thoughts and discussions about lighting that up, but there aren't any concrete plans just yet. It was actually on my mind in the earliest incarnations of this work a couple of years ago before Swagger was all the rave.
With that in mind, it was important to make sure that the API version metadata doesn't solely rely on attributes and can be easily be retrieved by other components such as extension points.
``` c#
ApiVersionModel serviceApiModel = controllerDescriptor.GetApiVersionModel();
ApiVersionModel actionApiModel = actionDescriptor.GetApiVersionModel();
if ( !serviceModel.IsApiVersionNeutral )
{
foreach ( var version in serviceApiModel.SupportedApiVersions )
{
// TODO:
}
foreach ( var version in serviceApiModel.DeprecatedApiVersions )
{
// TODO:
}
}
The collections of API versions have the following meanings:
| Collection | Meaning |
| --- | --- |
| IsApiVersionNeutral | The service is API version-neutral; all other collections will be empty |
| SupportedApiVersions | All supported API versions of a service |
| DeprecatedApiVersions | All deprecated API versions of a service |
| ImplementedApiVersions | All supported and deprecated API versions for a service |
| DeclaredApiVersions | The API versions explicitly declared by a specific controller or action; for documentation, this only interesting for filtering and matching actions. |
As it happens, the design of ASP.NET Web API never accounted for the fact that there could be multiple implementations of a route. There is handling for duplicate routes by default, but this is assumed to be always be a developer mistake. This made supporting API versioning in Web API a bit of a challenge and sometimes wonky. Fortunately, the model is much better in ASP.NET Core.
As a result of these assumptions, I had to introduce a special **HttpControllerDescriptorGroup** which aggregates all **HttpControllerDescriptor** for a service together. If you try to rely solely on the **IHttpControllerSelector.GetControllerMapping** method, it will make things a bit odd. In order for that to work, all of the similar controllers must have the same key, which there is no guarantee of. In addition, the API version aggregation work is only done at runtime because it's based off of the candidates the routing infrastructure provides. I've made all the related APIs and extensions methods public so you should be able to take advantage of them in one of a couple of ways:
### Option 1
``` c#
var controllerSelector = configuration.Services.GetHttpControllerSelector();
var versionMapping =
controllerSelector.GetControllerMapping()
.ToDictionary(
kvp => kvp.Key,
kvp =>
{
// this could be an HttpControllerDescriptorGroup
// which is enumerable. account for this without
// making a hard dependency on that type
var descriptors = kvp.Value as IEnumerable<HttpControllerDescriptor ??
new []{ kvp.Value };
return descriptors.Select( d => d.GetApiVersionModel() ).Aggregate();
} );
// TODO: use controller name to API version mapping
``` c#
var services = configuration.Services;
var assembliesResolver = services.GetAssembliesResolver();
var typeResolver = services.GetHttpControllerTypeResolver();
var controllerTypes = typeResolver.GetControllerTypes( assembliesResolver );
var controllerDescriptors = controllerTypes.Select( controllerType =>
new HttpControllerDescriptor(
configuration,
string.Empty,
controllerType ) );
// TODO: you need a way to logically group services together here.
// there's nothing out-of-the-box yet, but it will likely make sense
// at some point. maybe something like:
//
// [ApiServiceName( "HelloWorld" )]
IDictionary
var versionMapping =
groupedControllerDescriptors.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value.Select( d => d.GetApiVersionModel() ).Aggregate() );
// TODO: use controller name to API version mapping
```
When you inspect the API version model for actions, keep in mind that does not relate to routing or the version of the service. The API version for an action is only used for selecting which method to dispatch to when controllers have interleaved API versions. Don't interleave versions and things are much simpler. If you do want to support it, then just make sure you choose the action that has at least one explicitly matching, mapped API version. Actions cannot be version-neutral nor have deprecated versions. The DeclaredApiVersions property is probably the most useful here. Any action that does not have at least one explicitly mapped API version always implicitly matches the API version of the defining service (kind of like a method overload).
That should be enough to get you started. Let me know if you have more questions. If you come up with something generic, please share.
Nice amount of detail there, points me in the right direction. I'm glad I asked :)
Thanks for the response.
Is there a sample showing how to get this done? If not, would it be possible to add a really simple one in the project samples?
For anyone else coming here. looks like built in support using ASP.NET Core's abstraction layer is coming soon in https://github.com/Microsoft/aspnet-api-versioning/issues/60.
If you need full support today, there is this thread https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/244 with a full blown NuGet package you can use called SwashbuckleAspNetVersioningShim.
I think I'll be waiting for proper support.
It is indeed coming. I just updated PR #103 with the alpha support for ASP.NET Core. I think I have most of the right abstractions and setup in place for most scenarios. I have a sample project up so you can see how things will wire up. You'll want to look at Startup.cs and ImplicitApiVersionParameter.cs.
I'm avoiding any direct coupling on Swagger or Swashbuckle, which is why the ImplicitApiVersionParameter.cs is needed (unless I find an alternate way to include it). Fortunately, this class only requires minimal work to implement. It also varies greatly, depending on how you choose to implement API versioning. This might be better handled with a template or directly in Swashbuckle. While the API explorer work is meant to support Swagger and Swashbuckle, it could be used in other contexts as well.
I'm definitely looking for feedback to make sure this is what the community has really been asking for. Thus far, I think it's really close. If you want to try it, feel free to pull the branch down. Thanks.
Hi team,
Thanks for the code above on versioning , but i am facing an issue with the below code, please help if i am missing out,
1.The swagger generated doesn't show the any information about the second method on the swagger/docs/v2 generated.
2."version:apiVersion" occurs on the swagger ui :(, while we want it as v1.0 /hello and
v2.0/hello.
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class SampleController
{
[MapToApiVersion("1.0")]
[Route("version:apiVersion/hello")]
[HttpGet]
public string Get()
{
return "Hellov1";
}
}
[MapToApiVersion("2.0")]
[Route("version:apiVersion/hello")]
[HttpGet]
public string GetV2()
{
return "Hellov2";
}
}
@Ramya5108 you have not defined the route parameter and constraint correctly. The value must be surrounded with { }. If you want a v in your path, then you will need to add that to the template as well. This is what a well-formed route template should look like: v{version:apiVersion}/hello.
In order to integrate with Swagger/Swashuckle, you'll want the appropriate corresponding API explorer package. The links, by platform, can be found in the API Documentation topic.
It's worth noting that neither the API explorer nor Swashbuckle will translate your route template from v{version:apiVersion}/hello to v1/hello, just as it will not translate hello/{id} to hello/42. However, the API explorer will document the API version parameter for you and give it a default value based on the mapping so that a user doesn't have to input it within the Swagger UI. The name of the API version parameter is derived from the route template parameter (just like any other route template parameter). In this case, the value will be version. You can change it to any other valid name you choose. For example, v{api-version:apiVersion}/hello will have the parameter name api-version.
For additional information and end-to-end working examples, I recommend you review the API Documentation topic. There are links to the samples by platform.
Hi @commonsensesoftware ,
Thanks for the reponse , Sorry that i missed v{version:apiVersion}/hello in the sample above , while actual code had this.
From the links you have provided , i understand that ordercontroller lied on 3 different namespaces for each version , to me i am looking for the same controller , same namespace , supporting multiple versions .
As well , the samples on the link used swashbuckle .aspnetcore package and asp.net core application.While i use swashbuckle and asp.net web application.
i agree that routetemplateparameter will be unaware of the value, Could you please help me with a link on the sample to tweak the swagger on this?is that possible?
https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/244 am facing the same issue , while not with asp.net core application , but using asp.net web application.Please help @commonsensesoftware !!
@Ramya5108, the wiki links provided have the setup instructions and required NuGet packages for all ASP.NET platforms, including Web API. There are numerous, numerous combinations in which you might compose your application to support mapping code to services and API versions. You might use different namespaces, you might not. Ultimately, the API versioning libraries don't care which approach you use. They are able to discover your APIs and their versions. If you examine the V1 OrdersController, you'll see that it implements versions 0.9 and 1.0, which is the type of API version interleaving you are asking for. Both API versions will appear as separate entries in the Swagger document and UI. If you so desire, you put all of your services in a single namespace and apply API versions this way. The choice is yours.
Please review the complete end-to-end ASP.NET Web API with API Versioning and Swashbuckle sample.
I hope that helps.
Thanks @commonsensesoftware for your inputs