一、为什么要使用版本控制
(1)项目不停迭代,在增加新接口时不破坏原有接口,以保证webapi推出新功能后旧的移动客户端可顺利使用老接口
(2)限制移动端对接口的访问,为客户端提供额外的功能。
二、webapi版本控制方法
(1)在 URL 中追加版本或作为查询字符串参数
(2)通过自定义标头和通过接受标头
三、webapi版本控制实现
微软有开源的组件库,不用重复造轮子,aspnet-api-versioning
四、swagger查看各个版本的webapi
1.如何使用swagger来查看webapi文档,请参阅我的另一篇文章
2.如何查看不同版本有哪些接口,效果如下
我的版本控制方法基于Header实现,使用的是asp.net mvc,主要的代码已加粗
Startup.cs
public class Startup { public static string VersionName = "api-version"; /// <summary> /// Configures the application using the provided builder. /// </summary> /// <param name="builder">The current application builder.</param> public void Configuration( IAppBuilder builder ) { // we only need to change the default constraint resolver for services that want urls with versioning like: ~/v{version}/{controller} var constraintResolver = new DefaultInlineConstraintResolver() { ConstraintMap = { ["apiVersion"] = typeof( ApiVersionRouteConstraint ) } }; var configuration = new HttpConfiguration(); var httpServer = new HttpServer( configuration ); // reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions" configuration.AddApiVersioning( o => { o.ReportApiVersions = true; //采用header方式控制版本 o.ApiVersionReader = new HeaderApiVersionReader(Startup.VersionName); } ); configuration.MapHttpAttributeRoutes( constraintResolver ); //集成swagger var apiExplorer = configuration.AddVersionedApiExplorer( o => o.GroupNameFormat = "‘v‘VVV" ); configuration.EnableSwagger( "{apiVersion}/swagger", swagger => { //多版本选择配置 swagger.MultipleApiVersions( ( apiDescription, version ) => apiDescription.GetGroupName() == version, info => { foreach ( var group in apiExplorer.ApiDescriptions ) { var description = "A sample application with Swagger, Swashbuckle, and API versioning."; if ( group.IsDeprecated ) { description += " This API version has been deprecated."; } info.Version(group.Name, $"webapi文档 {group.ApiVersion}"); } } ); //调试时添加版本参数 swagger.DocumentFilter<ReplaceQueryVersionFilter>(); //为版本参数设置默认值 swagger.OperationFilter<SwaggerDefaultValuesFilter>(); //载入xml string xmlFile = XmlCommentsFilePath; swagger.IncludeXmlComments(xmlFile); } ) .EnableSwaggerUi( swagger => { swagger.EnableDiscoveryUrlSelector(); }); builder.UseWebApi( httpServer ); } static string XmlCommentsFilePath { get { var basePath = System.AppDomain.CurrentDomain.RelativeSearchPath; var fileName = typeof( Startup ).GetTypeInfo().Assembly.GetName().Name + ".xml"; return Path.Combine( basePath, fileName ); } }
ReplaceQueryVersionFilter.cs
/// <summary> /// 将query中的版本号参数更换为head请求方式 /// </summary> public class ReplaceQueryVersionFilter : IDocumentFilter { public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer) { foreach (PathItem path in swaggerDoc.paths.Values) { var versionParam = path.get.parameters.FirstOrDefault(i => i.name == Startup.VersionName); if (versionParam != null) { var index = path.get.parameters.IndexOf(versionParam); path.get.parameters[index].@in = "header"; } } } }
SwaggerDefaultValuesFilter.cs
public class SwaggerDefaultValuesFilter : IOperationFilter { /// <summary> /// Applies the filter to the specified operation using the given context. /// </summary> /// <param name="operation">The operation to apply the filter to.</param> /// <param name="schemaRegistry">The API schema registry.</param> /// <param name="apiDescription">The API description being filtered.</param> public void Apply( Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription ) { if ( operation.parameters == null ) { return; } foreach ( var parameter in operation.parameters ) { try { var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.name); // REF: https://github.com/domaindrivendev/Swashbuckle/issues/1101 if (parameter.description == null) { parameter.description = description.Documentation; } // REF: https://github.com/domaindrivendev/Swashbuckle/issues/1089 // REF: https://github.com/domaindrivendev/Swashbuckle/pull/1090 if (parameter.@default == null) { parameter.@default = description.ParameterDescriptor.DefaultValue; } } catch { } } } }