NSwag:从 API 的多个版本生成 C# 客户端
NSwag: Generate C# Client from multiple Versions of an API
我们正在对 API 进行版本控制,并在 ASP.NET Core 1.1 中使用 Swashbuckle 生成 Swagger 规范。我们可以根据那些 JSON 规范文件生成两个 API 文档:
<!-- 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
}
我们将所有操作都包含在两个规范文件中,除非使用 [MapToApiVersion]
和 ApiExplorerSettings(GroupName ="<version>")]
属性将其映射到特定版本。仅属于旧版本的方法也用 [Obsolete]
属性修饰:
<!-- language: c# -->
[MapToApiVersion("0.1")]
[ApiExplorerSettings(GroupName = "0.1")]
[Obsolete]
但是,我们只想从两个规范文件的联合生成一个 C# 客户端,其中所有方法都包含在客户端中,0.1 和 0.2,但所有过时的方法都标记为过时.
我研究了 NSwag(我们已经使用了很长一段时间了)和 AutoRest。 AutoRest 似乎 support a merging scenario,但由于模式验证错误我无法让它工作(我非常不确定我们的特定场景是否会得到实际支持)。
到目前为止,我最后的想法是以某种方式JSON-将规范合并为一个,然后将其提供给 NSwag。
我们在这里遗漏了什么吗?这有可能通过 NSwag 实现吗?
这是我的想法,根据评论进行扩展:
使用 swashbuckle,您可以根据需要生成任意数量的 SwaggerDoc,在这种情况下,我们的想法是生成 3 个,保留与您拥有的 2 个版本相同的版本,然后再添加一个包含所有内容的版本。
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");
});
这是一个工作示例:
http://swagger-net-test-multiapiversions.azurewebsites.net/swagger/ui/index?filter=Api
那个项目的全部代码在 GitHub:
https://github.com/heldersepu/Swagger-Net-Test/tree/MultiApiVersions
包:
安装包Swashbuckle.AspNetCore
安装包Microsoft.AspNetCore.Mvc.Versioning
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
);
}
}
我写了一篇关于类似问题的文章https://medium.com/dev-genius/nswag-charp-client-from-multiple-api-versions-7c79a3de4622
首先,创建一个模式。如我所见,有两种方法:
- 存在多个版本的一个架构
- 每个版本都有自己的架构
接下来,为每个受支持的版本创建客户端并将它们包装在包装器客户端下:
public class AppApiClient
{
public IV1Client V1 { get; }
public IV2Client V2 { get; }
public AppApiClient(HttpClient httpClient)
{
V1 = new V1Client(httpClient);
V2 = new V2Client(httpClient);
}
}
我们正在对 API 进行版本控制,并在 ASP.NET Core 1.1 中使用 Swashbuckle 生成 Swagger 规范。我们可以根据那些 JSON 规范文件生成两个 API 文档:
<!-- 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
}
我们将所有操作都包含在两个规范文件中,除非使用 [MapToApiVersion]
和 ApiExplorerSettings(GroupName ="<version>")]
属性将其映射到特定版本。仅属于旧版本的方法也用 [Obsolete]
属性修饰:
<!-- language: c# -->
[MapToApiVersion("0.1")]
[ApiExplorerSettings(GroupName = "0.1")]
[Obsolete]
但是,我们只想从两个规范文件的联合生成一个 C# 客户端,其中所有方法都包含在客户端中,0.1 和 0.2,但所有过时的方法都标记为过时.
我研究了 NSwag(我们已经使用了很长一段时间了)和 AutoRest。 AutoRest 似乎 support a merging scenario,但由于模式验证错误我无法让它工作(我非常不确定我们的特定场景是否会得到实际支持)。
到目前为止,我最后的想法是以某种方式JSON-将规范合并为一个,然后将其提供给 NSwag。
我们在这里遗漏了什么吗?这有可能通过 NSwag 实现吗?
这是我的想法,根据评论进行扩展:
使用 swashbuckle,您可以根据需要生成任意数量的 SwaggerDoc,在这种情况下,我们的想法是生成 3 个,保留与您拥有的 2 个版本相同的版本,然后再添加一个包含所有内容的版本。
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");
});
这是一个工作示例:
http://swagger-net-test-multiapiversions.azurewebsites.net/swagger/ui/index?filter=Api
那个项目的全部代码在 GitHub:
https://github.com/heldersepu/Swagger-Net-Test/tree/MultiApiVersions
包:
安装包Swashbuckle.AspNetCore
安装包Microsoft.AspNetCore.Mvc.Versioning
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
);
}
}
我写了一篇关于类似问题的文章https://medium.com/dev-genius/nswag-charp-client-from-multiple-api-versions-7c79a3de4622
首先,创建一个模式。如我所见,有两种方法:
- 存在多个版本的一个架构
- 每个版本都有自己的架构
接下来,为每个受支持的版本创建客户端并将它们包装在包装器客户端下:
public class AppApiClient
{
public IV1Client V1 { get; }
public IV2Client V2 { get; }
public AppApiClient(HttpClient httpClient)
{
V1 = new V1Client(httpClient);
V2 = new V2Client(httpClient);
}
}