Please help me with this, it looked easy at first, now I'm late in the project:
I'm trying to setup API versioning for a ASP.NET WebApi project, along with Swagger. The API versioning works fine, calling different versions returns the correct results (see below).
On the contrary, Swagger fails to serve both versions. While debugging, I noticed that when c.MultipleApiVersions(...)
gets called in SwaggerConfig.cs, the controller reported by apiDesc.ActionDescriptor.ControllerDescriptor
is always PingController
and never Ping11Controller
.
Can somebody point out what needs to be done to solve this and have Swagger also work for both versions?
Below, the code and proof of API versioning working fine while Swagger working only for v1.0.
Thank you!
Calling API v1.0 works:
Calling API v1.1 works too:
Swagger for v1.0 is good: (http://localhost:50884/v1.0/swagger)
{
"swagger":"2.0",
"info":{
"version":"v1.0",
"title":"My API v1.0"
},
"host":"localhost:50884",
"schemes":[
"http"
],
"paths":{
"/api/ping":{
"get":{
"tags":[
"Ping"
],
"summary":"Get a pong.",
"operationId":"GetAPong",
"consumes":[
],
"produces":[
"application/json",
"text/json",
"application/xml",
"text/xml"
],
"responses":{
"200":{
"description":"OK"
},
"404":{
"description":"NotFound"
}
}
}
}
},
"definitions":{
}
}
Swagger for v1.1 is empty: (http://localhost:50884/v1.1/swagger)
{
"swagger":"2.0",
"info":{
"version":"v1.1",
"title":"My API v1.1"
},
"host":"localhost:50884",
"schemes":[
"http"
],
"paths":{
},
"definitions":{
}
}
App_Start\WebApiConfig.cs:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.AddApiVersioning(options => {
options.ReportApiVersions = true;
});
var constraintResolver = new System.Web.Http.Routing.DefaultInlineConstraintResolver();
constraintResolver.ConstraintMap.Add("apiVersion", typeof(Microsoft.Web.Http.Routing.ApiVersionRouteConstraint));
config.MapHttpAttributeRoutes(constraintResolver);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
App_Start\SwaggerConfig.cs:
public class SwaggerConfig
{
static string XmlCommentsFilePath
{
get
{
var basePath = System.AppDomain.CurrentDomain.RelativeSearchPath;
var fileName = typeof(SwaggerConfig).GetTypeInfo().Assembly.GetName().Name + ".xml";
return Path.Combine(basePath, fileName);
}
}
public static void Register()
{
var configuration = GlobalConfiguration.Configuration;
GlobalConfiguration.Configuration.EnableSwagger("{apiVersion}/swagger", c => {
c.OperationFilter<SwaggerDefaultValues>();
c.MultipleApiVersions((System.Web.Http.Description.ApiDescription apiDesc, string targetApiVersion) =>
{
var attr = apiDesc.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<Microsoft.Web.Http.ApiVersionAttribute>().FirstOrDefault();
if (attr == null && (targetApiVersion == "v1" || targetApiVersion == "v1.0")) return true;
var match = (attr != null) && (attr.Versions.FirstOrDefault(v => "v" + v.ToString() == targetApiVersion) != null);
return match;
},
(vc) =>
{
vc.Version("v1.1", "My API v1.1");
vc.Version("v1.0", "My API v1.0");
});
c.IncludeXmlComments(SwaggerConfig.XmlCommentsFilePath);
})
.EnableSwaggerUi(c => {
c.DocExpansion(DocExpansion.List);
c.EnableDiscoveryUrlSelector();
});
}
}
Controllers for v1.0 and v1.1 (sitting in the same namespace)
[ApiVersion("1.0")]
[RoutePrefix("api")]
[ControllerName("Ping")]
public class PingController : ApiController
{
[HttpGet]
[Route("ping")]
[SwaggerOperation("GetAPong")]
[SwaggerResponse(HttpStatusCode.OK)]
[SwaggerResponse(HttpStatusCode.NotFound)]
public string Get()
{
return "Pong v1.0";
}
}
[ApiVersion("1.1")]
[RoutePrefix("api")]
[ControllerName("Ping")]
public class Ping11Controller : ApiController
{
[HttpGet]
[Route("ping")]
[SwaggerOperation("GetAPong")]
[SwaggerResponse(HttpStatusCode.OK)]
[SwaggerResponse(HttpStatusCode.NotFound)]
public string Get()
{
return "Pong v1.1";
}
}
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.AspNet.WebApi" version="5.2.3" targetFramework="net46" />
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net46" />
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net46" />
<package id="Microsoft.AspNet.WebApi.Versioning" version="2.1.0" targetFramework="net46" />
<package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.3" targetFramework="net46" />
<package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="1.0.7" targetFramework="net46" />
<package id="Microsoft.IdentityModel.Logging" version="1.1.4" targetFramework="net46" />
<package id="Microsoft.IdentityModel.Tokens" version="5.1.4" targetFramework="net46" />
<package id="Microsoft.Net.Compilers" version="2.3.2" targetFramework="net46" developmentDependency="true" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net46" />
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net46" />
<package id="NLog" version="4.4.12" targetFramework="net46" />
<package id="Swashbuckle" version="5.6.0" targetFramework="net46" />
<package id="Swashbuckle.Core" version="5.6.0" targetFramework="net46" />
<package id="System.IdentityModel.Tokens.Jwt" version="5.1.4" targetFramework="net46" />
<package id="WebActivatorEx" version="2.2.0" targetFramework="net46" />
</packages>
Solved it by:
Using the versioned API explorer as below (note that I had to move the code from SwaggerConfig.cs in WebApiConfig.cs due to initialization issues):
var apiExplorer = config.AddVersionedApiExplorer(options => {
options.GroupNameFormat = "'v'VVV";
});
var versionSupportResolver = new Func<ApiDescription, string, bool>((apiDescription, version) => apiDescription.GetGroupName() == version);
var versionInfoBuilder = new Action<VersionInfoBuilder>(info => {
foreach (var group in apiExplorer.ApiDescriptions)
{
info.Version(group.Name, $"MyAPI v{group.ApiVersion}");
}
});
config
.EnableSwagger("{apiVersion}/swagger", swagger => {
swagger.OperationFilter<SwaggerDefaultValues>();
swagger.MultipleApiVersions(versionSupportResolver, versionInfoBuilder);
swagger.IncludeXmlComments(WebApiConfig.XmlCommentsFilePath);
})
.EnableSwaggerUi(swaggerUi => {
swaggerUi.EnableDiscoveryUrlSelector();
swaggerUi.DocExpansion(DocExpansion.List);
});
ValueV1Controller.cs
[RoutePrefix("api/v1/value")]
public class ValueV1Controller : ApiController
{
[Route("get")]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
ValueV2Controller.cs
[RoutePrefix("api/v2/value")]
public class ValueV2Controller : ApiController
{
[Route("get")]
public IEnumerable<string> Get()
{
return new string[] { "value1.2", "value2.2" };
}
}
SwaggerConfig.cs
public class SwaggerConfig
{
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.MultipleApiVersions(
(apiDesc, version) =>
{
var path = apiDesc.RelativePath.Split('/');
var pathVersion = path[1];
return CultureInfo.InvariantCulture.CompareInfo.IndexOf(pathVersion, version, CompareOptions.IgnoreCase) >= 0;
},
(vc) =>
{
vc.Version("v2", "Swashbuckle Dummy API V2");
vc.Version("v1", "Swashbuckle Dummy API V1");
});
})
.EnableSwaggerUi(c =>
{
c.EnableDiscoveryUrlSelector();
});
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With