资讯详情

版本即契约:.NET 6 WebAPI 多版本治理与 Swagger 文档分治实践

📅 2026/11/4 19:58:13 | 华诺云谱 👁 阅读
版本即契约:.NET 6 WebAPI 多版本治理与 Swagger 文档分治实践
Asp.Versioning 官方文档https://dotnet.github.io/aspnet-api-versioning本文使用的是Asp.Versioning.Mvc 6.4.1对应 .NET 6 的稳定线。先给结论网上大量教程说Swagger 多版本必须引Asp.Versioning.Mvc.ApiExplorer——这句话只在自动化分组路线下成立即依赖ApiDescription.GroupName或IApiVersionDescriptionProvider去枚举版本。本文走的是另一条 Swashbuckle 原生路线DocInclusionPredicate直接反射控制器上的[ApiVersion]完成归组整条链路不经过IApiExplorer的版本增强层所以那个包在这里是冗余的。少一个依赖筛选逻辑全部摊在明面上版本清单只有一个来源。下面是完整代码。装包一、Swagger 配置using Asp.Versioning; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; namespace Extensions_Api.Configurations; /// summarySwagger 配置。/summary public static class SwaggerConfigExtensions { /// summary注册 Swagger 的生成规则。/summary public static IServiceCollection AddSwaggerConfig(this IServiceCollection services, string[] apiVersions) { services.AddSwaggerGen(options { // 有 XML 注释文件时才加载没有文件不影响启动。 var xmlFile ${System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml; var xmlPath Path.Combine(AppContext.BaseDirectory, xmlFile); if (File.Exists(xmlPath)) { options.IncludeXmlComments(xmlPath, true); } // 给路径里的 version 参数填上默认值。 options.ParameterFilterVersionDefaultValueFilter(); // 每个版本单独生成一份 Swagger 文档。 foreach (var version in apiVersions) { options.SwaggerDoc(version, new OpenApiInfo { Title $API {version.ToUpper()}, Version version }); } // 同一路径有多个接口时取第一个避免文档生成失败。 options.ResolveConflictingActions(api api.First()); // 按控制器上的 ApiVersion 把接口分到对应版本文档。 options.DocInclusionPredicate((docName, apiDesc) { if (!apiDesc.TryGetMethodInfo(out var methodInfo)) { return false; } var versions methodInfo.DeclaringType? .GetCustomAttributes(true) .OfTypeApiVersionAttribute() .SelectMany(attr attr.Versions) .Select(version $v{version.MajorVersion}) .ToList(); return versions?.Contains(docName) ?? false; }); // Swagger 右上角显示 JWT 输入框Token 放在 Authorization 请求头。 options.AddSecurityDefinition(Bearer, new OpenApiSecurityScheme { Description JWT授权, Name Authorization, In ParameterLocation.Header, Type SecuritySchemeType.ApiKey, Scheme Bearer }); // 所有接口都带上 Bearer 认证否则调试时不会提交 Token。 options.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference new OpenApiReference { Type ReferenceType.SecurityScheme, Id Bearer } }, new Liststring() } }); }); return services; } /// summary启用 Swagger 和按版本切换的 Swagger UI。/summary public static WebApplication UseSwaggerConfig(this WebApplication app, string[] apiVersions) { // 只在开发环境开放 Swagger。 if (app.Environment.IsDevelopment()) { app.UseSwagger(); // 右上角按 v1、v2 切换文档。 app.UseSwaggerUI(options { foreach (var version in apiVersions) { options.SwaggerEndpoint($/swagger/{version}/swagger.json, version); } }); } return app; } } /// summarySwagger 参数过滤器为 API 版本号参数设置默认值。/summary public class VersionDefaultValueFilter : IParameterFilter { /// summary为路径中的 version 参数填充默认值。/summary public void Apply(OpenApiParameter parameter, ParameterFilterContext context) { if (parameter.Name version parameter.In ParameterLocation.Path parameter.Schema ! null) { var version context.DocumentName.StartsWith(v, StringComparison.OrdinalIgnoreCase) ? context.DocumentName[1..] : context.DocumentName; parameter.Schema.Default new OpenApiString(version); parameter.Example new OpenApiString(version); } } }要点DocInclusionPredicate四步走——拿MethodInfo、反查DeclaringType、收集控制器上的ApiVersionAttribute、用MajorVersion拼成v1/v2比对。不碰IApiVersionDescriptionProvider所以不需要Asp.Versioning.Mvc.ApiExplorer。VersionDefaultValueFilter从DocumentName反推版本号同时填Default和ExampleSwagger UI 里 Try it out 时{version}已经预填好不用手动敲。apiVersions是唯一版本清单SwaggerDoc和SwaggerEndpoint都从它派生不存在两处列表不同步的隐患。IsDevelopment()收在扩展内生产环境天然不暴露文档。二、API 版本控制配置using Asp.Versioning; namespace Extensions_Api.Configurations; /// summaryAPI 版本配置。/summary public static class ApiVersioningConfigExtensions { /// summary /// 注册 API 版本规则。未传版本时默认使用 1.0并在响应头中返回支持的版本。 /// /summary public static IServiceCollection AddApiVersioningConfig(this IServiceCollection services) { services.AddApiVersioning(options { // 请求未带版本号时使用的默认版本。 options.DefaultApiVersion new ApiVersion(1, 0); // 未传版本时按默认版本处理避免直接返回 400。 options.AssumeDefaultVersionWhenUnspecified true; // 在响应头中返回当前接口支持的版本。 options.ReportApiVersions true; }).AddMvc(); return services; } }要点DefaultApiVersion配AssumeDefaultVersionWhenUnspecified不带版本的请求回退到 v1而不是 400。ReportApiVersions让响应头带上api-supported-versions: 1.0, 2.0调用方据此可以做灰度切换。末尾的.AddMvc()是整条链路的开关——这一句漏掉前面所有配置全部失效也是新手最常踩的坑。三、Program.csusing Extensions_Api.Configurations; // 创建应用构建器读取配置、环境和依赖注入容器。 var builder WebApplication.CreateBuilder(args); // 当前开放的 API 版本Swagger 按这些版本分别生成文档。 var apiVersions new[] { v1, v2 }; // 注册控制器。 builder.Services.AddControllers(); // 注册接口发现供 Swagger 读取路由和参数。 builder.Services.AddEndpointsApiExplorer(); // 注册 API 版本规则未传版本时默认 1.0。 builder.Services.AddApiVersioningConfig(); // 按版本生成 Swagger 文档并加上 JWT 输入框。 builder.Services.AddSwaggerConfig(apiVersions); // 构建应用之后开始配置请求管道。 var app builder.Build(); // 开发环境启用 Swagger右上角可切换 v1、v2。 app.UseSwaggerConfig(apiVersions); // HTTP 请求重定向到 HTTPS。 app.UseHttpsRedirection(); // 启用授权中间件。 app.UseAuthorization(); // 把控制器路由映射到终结点。 app.MapControllers(); // 启动应用并开始监听请求。 app.Run();要点apiVersions是全链路唯一的版本来源——版本控制默认值、Swagger 文档生成、UI 下拉端点全部从这里派生。这不是省事是消除不一致的根因。AddEndpointsApiExplorer()提供ApiDescription所需的MethodInfo是DocInclusionPredicate正常工作的前提不能漏。四、控制器示例// V1 [ApiController] [ApiVersion(1.0)] [Route(api/v{version:apiVersion}/[controller])] public class WeatherForecastController : ControllerBase { /* 返回原始数组 */ } // V2 [ApiController] [ApiVersion(2.0)] [Route(api/v{version:apiVersion}/[controller])] public class WeatherForecastController : ControllerBase { /* 返回 WeatherForecastV2Result */ }要点{version:apiVersion}是路由约束告诉路由系统这一段由版本控制逻辑解析而不是当普通字符串。两个版本返回结构可以完全不同——V1 返回原始数组V2 包一层WeatherForecastV2Result带Version字段。契约能独立演进而不炸老客户端这才是多版本控制存在的意义不然加个v1只是自欺欺人。五、跑起来验证什么访问/swagger右上角切版本。逐项验证切到 v1 只看到 v1 接口切到 v2 只看到 v2 接口且 v2 的响应 schema 是WeatherForecastV2Result点开接口时version参数已自动预填Try it out 不用手动敲不带版本直接请求/api/weatherforecast回退到 v1验证AssumeDefaultVersionWhenUnspecified生效响应头带api-supported-versions: 1.0, 2.0把环境切到非 DevelopmentSwagger 端点返回 404验证环境隔离生效。多版本控制的价值从来不是路由里多一个v1而是它给契约演进提供了制度化的空间老客户端继续走老契约新客户端接入新契约Swagger 文档如实反映当前版本的全貌联调方不再靠猜。这套方案的几个关键决策值得带走决策点选择理由版本控制库Asp.Versioning.Mvc 6.4.1.NET 6 下的现行主线Swagger 分组DocInclusionPredicate手动筛选依赖面小、逻辑显式、与版本清单天然对齐是否引入 ApiExplorer不需要手动路线绕开IApiExplorer版本增强层版本参数预填IParameterFilter对所有接口一次性生效零侵入版本来源单一apiVersions数组避免多处硬编码不同步Swagger 暴露扩展内IsDevelopment()隔离生产不暴露文档职责内聚如果后续版本粒度从1.0、2.0扩展到次版本只需调整apiVersions数组与Select的拼接方式整体结构无需重写。一个自洽的体系应该在需求演进时保持骨架不动——这比能跑值钱得多。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑