NSwag: Generate C# Client from multiple Versions of an API


NSwag: Generate C# Client from multiple Versions of an API



We are versioning our API and generating the Swagger specification using Swashbuckle in ASP.NET Core 1.1. We can generate two API docs based on those JSON specification files:


<!-- language: c# -->
services.AddSwaggerGen(setupAction =>
{
setupAction.SwaggerDoc("0.1", new Info { Title = "Api", Version = "0.1", Description = "API v0.1" });
setupAction.SwaggerDoc("0.2", new Info { Title = "Api", Version = "0.2", Description = "API v0.2" });

// more configuration omitted
}



We are including all actions in both spec files, unless it is mapped to a specific version using the [MapToApiVersion] and ApiExplorerSettings(GroupName ="<version>")] attributes. Methods belonging to an older version only are also decorated with the [Obsolete] attribute:


[MapToApiVersion]


ApiExplorerSettings(GroupName ="<version>")]


[Obsolete]


<!-- language: c# -->
[MapToApiVersion("0.1")]
[ApiExplorerSettings(GroupName = "0.1")]
[Obsolete]



However, we want to have only one C# Client generated from the Union of both spec files, where all methods are included in the Client, 0.1 as well as 0.2, but all obsolete methods marked, in fact, as obsolete.



I have looked into both NSwag (which we are using for quite some time now) as well as AutoRest. AutoRest seems to support a merging scenario, but I could not get it to work because of schema validation errors (and I am more than unsure whether our specific scenario would be actually supported).



My last idea as of now to get this sorted is to somehow JSON-merge the specs into one and then feed it to NSwag.



Do we miss anything here? Is this somehow possible to realize with NSwag?





So if I understand correctly, you're currently using Swashbuckle and want to migrate to NSwag/AutoRest?
– Baksteen
May 30 at 12:02





No. We are generating the spec (JSON) with Swashbuckle.AspNetCore, and generating the C# client with NSwag. We do not want to migrate anything, if possible.
– Structed
May 30 at 12:06





I think you can do that on Swashbuckle, take a look at this one: swagger-net-test-multiapiversions.azurewebsites.net/swagger/ui/…
– HelderSepu
May 31 at 21:00






@HelderSepu that looks pretty like the thing I need. From the default, I would then generate the client, I guess. Do you have a link to the particular docs or your example code? Thank you!
– Structed
Jun 1 at 5:28





2 Answers
2



Here is my idea, expanding from the comments:



With swashbuckle you can generate as many SwaggerDoc as you like, the idea on this case is to generate 3 keep the same 2 versions that you have and add one more that will have everything.


c.MultipleApiVersions(
(apiDesc, targetApiVersion) =>
targetApiVersion.Equals("default") || // Include everything by default
apiDesc.Route.RouteTemplate.StartsWith(targetApiVersion), // Only include matching routes for other versions
(vc) =>
{
vc.Version("default", "Swagger_Test");
vc.Version("v1_0", "Swagger_Test V1_0");
vc.Version("v2_0", "Swagger_Test V2_0");
});



Here is a working sample:
http://swagger-net-test-multiapiversions.azurewebsites.net/swagger/ui/index?filter=Api



And the entire code for that project is on GitHub:
https://github.com/heldersepu/Swagger-Net-Test/tree/MultiApiVersions





Thank you! However you example seems to be from Swashbuckle for the full .NET Framework, but I was looking into ASP.NET Core with Swashbuckle.ASpNetCore. There, I cannot replicate your solution.
– Structed
Jun 4 at 7:41



Packages:



Install-Package Swashbuckle.AspNetCore



Install-Package Microsoft.AspNetCore.Mvc.Versioning



enter image description here



ValueV1Controller.cs


[ApiVersion("1")]
[Route("api/v{version:apiVersion}/Values")]
public class ValuesV1Controller : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string { "value1", "value2" };
}
}



ValueV2Controller.cs


[ApiVersion("2")]
[Route("api/v{version:apiVersion}/Values")]
public class ValuesV2Controller : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string { "value1.2", "value2.2" };
}
}



Startup.cs


public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddApiVersioning();
// Register the Swagger generator, defining 1 or more Swagger documents
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "My API - V1", Version = "v1" });
c.SwaggerDoc("v2", new Info { Title = "My API - V2", Version = "v2" });

c.DocInclusionPredicate((docName, apiDesc) =>
{
var versions = apiDesc.ControllerAttributes()
.OfType<ApiVersionAttribute>()
.SelectMany(attr => attr.Versions);

return versions.Any(v => $"v{v.ToString()}" == docName);
});

c.OperationFilter<RemoveVersionParameters>();
c.DocumentFilter<SetVersionInPaths>();
});
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();

// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v2/swagger.json", "My API V2");
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});

app.UseMvc();
}
}

public class RemoveVersionParameters : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
var versionParameter = operation.Parameters?.SingleOrDefault(p => p.Name == "version");
if (versionParameter != null)
operation.Parameters.Remove(versionParameter);
}
}

public class SetVersionInPaths : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
{
swaggerDoc.Paths = swaggerDoc.Paths
.ToDictionary(
path => path.Key.Replace("v{version}", swaggerDoc.Info.Version),
path => path.Value
);
}
}






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

List of Kim Possible characters

Audio Livestreaming with Python & Flask