当前位置: 首页 > news >正文

API 版本控制:使用 ABP vNext 实现版本化 API 系统

🚀API 版本控制:使用 ABP vNext 实现版本化 API 系统


📚 目录

  • 🚀API 版本控制:使用 ABP vNext 实现版本化 API 系统
    • 一、背景切入 🧭
    • 二、核心配置规则 📋
      • 2.1 前置准备:NuGet 包与 `using` 📦
      • 2.2 启用版本化服务 🔧
        • 2.1.1 版本解析流程图 🗺️
        • 2.1.2 `RemoveVersionFromParameter` 实现 🛠️
        • 2.1.3 `ReplaceVersionWithExactValueInPath` 实现 🔄
      • 2.2 声明支持的版本 📝
      • 2.3 客户端代理与 Swagger 分组生成 ⚙️
        • 2.3.1 手动执行命令
        • 2.3.2 自动生成配置
        • 2.3.3 Swagger 文档生成流程图 📊
    • 三、实战演示 🎬
      • 3.1 URL Segment 模式(示例一:同类分支)🔀
      • 3.2 URL Segment 模式(示例二:拆分控制器)✂️
      • 3.3 QueryString 模式 🔍
      • 3.4 Header 模式 📬
      • 3.5 三种模式对比表 📋


一、背景切入 🧭

在需求快速迭代的时代,API 不再是一次性设计后一劳永逸的产物。为了保证旧版本客户端继续运行,同时平滑引入新功能,API 版本化(API Versioning) 就成为了一项必不可少的技术手段。🔧

ABP vNext 依托 ASP.NET Core 的版本化机制,提供了高强度可配置、完善体系化的版本控制解决方案。其底层实质是对 Microsoft.AspNetCore.Mvc.Versioning 的一层封装,在 ABP 框架下可以一行代码启用版本化,同时兼容 ABP 模块化、依赖注入等特性。本文将结合实际场景,详细讲解如何配置 ABP vNext 的 API 版本化支持,并通过实战代码展示各种版本读取方式与 Swagger 分组生成。✨


二、核心配置规则 📋

2.1 前置准备:NuGet 包与 using 📦

在开始配置之前,请先确保项目已经添加了以下 NuGet 包(示例版本号可根据实际情况调整)——直接在 .csproj 文件中加入下面的 <PackageReference>,或通过 dotnet add package 命令安装:

<PackageReference Include="Volo.Abp.AspNetCore.Mvc.Versioning" Version="4.6.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />

在代码文件顶部,需要引用以下命名空间,确保示例能够正常编译:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.Versioning;
using Volo.Abp.Modularity;
using System.Linq;

2.2 启用版本化服务 🔧

YourProject.HttpApi.Host 项目的 YourProjectHttpApiHostModule 中,覆盖 ConfigureServices 方法,调用 AddAbpApiVersioning 配置版本化选项。同时配置 Swagger 分组以生成多版本文档。示例如下:

namespace YourProject.HttpApi.Host
{[DependsOn(typeof(AbpAspNetCoreMvcModule),typeof(AbpAspNetCoreMvcVersioningModule)  // 自动引入 Microsoft.AspNetCore.Mvc.Versioning)]public class YourProjectHttpApiHostModule : AbpModule{public override void ConfigureServices(ServiceConfigurationContext context){// 注册 API 版本化服务context.Services.AddAbpApiVersioning(options =>{// 默认使用 1.0 版本options.DefaultApiVersion = new ApiVersion(1, 0);// 如果未指定版本,采用默认版本options.AssumeDefaultVersionWhenUnspecified = true;// 返回当前支持的版本信息到响应头 (api-supported-versions)options.ReportApiVersions = true;// 支持 URL Segment、QueryString、Header 三种方式读取版本// 顺序决定优先级:先按 URL Segment,再按 QueryString,最后按 Headeroptions.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(),           // /api/v1.0/...new QueryStringApiVersionReader("v"),       // /api/... ?v=1.0new HeaderApiVersionReader("x-api-version") // Header: x-api-version: 1.0);});// 注册 Swagger 分组支持context.Services.AddSwaggerGen(options =>{// 为每个版本定义一个 Swagger 文档options.SwaggerDoc("v1.0", new OpenApiInfo{Title = "Your API V1.0",Version = "v1.0"});options.SwaggerDoc("v2.0", new OpenApiInfo{Title = "Your API V2.0",Version = "v2.0"});// 按 GroupName 过滤 APIoptions.DocInclusionPredicate((docName, apiDesc) =>{// 如果没有 ApiVersionAttribute,也不是中立版本,则不包含var hasVersionAttribute = apiDesc.CustomAttributes().OfType<ApiVersionAttribute>().Any();var isNeutral = apiDesc.CustomAttributes().OfType<ApiVersionNeutralAttribute>().Any();if (isNeutral){// 将中立版本也展示在所有文档中return true;}if (!hasVersionAttribute){return false;}// 取得所有标注的版本号,例如 "1.0", "2.0"var versions = apiDesc.CustomAttributes().OfType<ApiVersionAttribute>().SelectMany(attr => attr.Versions).Select(v => $"v{v.ToString()}");// 只包含与当前 docName(如 "v1.0")匹配的 APIreturn versions.Contains(docName);});// 移除版本参数(避免在 Swagger UI 中显示 {version} 占位符)options.OperationFilter<RemoveVersionFromParameter>();// 将路径中的 {version:apiVersion} 占位符替换为具体版本号options.DocumentFilter<ReplaceVersionWithExactValueInPath>();});}}
}

说明

  1. AbpAspNetCoreMvcVersioningModule 模块会自动引入 Microsoft.AspNetCore.Mvc.Versioning,无需额外手动添加。
  2. 若只想使用单一的版本读取方式(如仅用 QueryString),可在 ApiVersionReader.Combine(...) 中删除多余的 Reader。
  3. RemoveVersionFromParameterReplaceVersionWithExactValueInPath 的实现可参考下文示例或 官方文档。

2.1.1 版本解析流程图 🗺️
有版本号
无版本号
有参数 ?v=
无参数
有 Header
无 Header
客户端请求
检查 URL Segment?
使用 URL Segment 版本
检查 QueryString?
使用 QueryString 版本
检查 Header?
使用 Header 版本
使用默认版本 (v1.0)
进入对应版本逻辑

2.1.2 RemoveVersionFromParameter 实现 🛠️
// 移除 Swagger 中的 version 参数
public class RemoveVersionFromParameter : IOperationFilter
{public void Apply(OpenApiOperation operation, OperationFilterContext context){if (operation.Parameters == null){return;}var versionParameter = operation.Parameters.FirstOrDefault(p => p.Name.Equals("version", StringComparison.InvariantCultureIgnoreCase));if (versionParameter != null){operation.Parameters.Remove(versionParameter);}}
}

2.1.3 ReplaceVersionWithExactValueInPath 实现 🔄
// 将路径中的 {version:apiVersion} 占位符替换为具体版本号
public class ReplaceVersionWithExactValueInPath : IDocumentFilter
{public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context){var paths = new OpenApiPaths();foreach (var (key, value) in swaggerDoc.Paths){// 将路径中的占位符 {version} 替换为 swaggerDoc.Info.Version(例如 "v1.0")var updatedKey = key.Replace("{version}", swaggerDoc.Info.Version);paths.Add(updatedKey, value);}swaggerDoc.Paths = paths;}
}

2.2 声明支持的版本 📝

当某个 Controller 需要同时响应多个版本时,可在类上使用 [ApiVersion] 标注,并在方法上使用 MapToApiVersion 指定具体版本。示例如下:

using Microsoft.AspNetCore.Mvc;namespace YourProject.HttpApi.Controllers
{// 同时支持 v1.0 和 v2.0[ApiVersion("1.0")][ApiVersion("2.0")][Route("api/v{version:apiVersion}/products")]public class ProductController : ControllerBase{// 仅在 v1.0 时暴露该方法[HttpGet, MapToApiVersion("1.0")]public IActionResult GetV1(){return Ok("Product from v1.0");}// 仅在 v2.0 时暴露该方法[HttpGet, MapToApiVersion("2.0")]public IActionResult GetV2(){return Ok("Product from v2.0");}}
}
  • 说明
    • 路由路径为 /api/v{version}/products,版本号通过 URL Segment 方式传递(如 /api/v1.0/products/api/v2.0/products)。
    • 如果在同一个 Controller 内逻辑分支较多,可使用 MapToApiVersion 在同一个类中实现多版本映射,避免类数量过多。
    • 若想做“版本中立”(即对所有版本通用),在类上使用 [ApiVersionNeutral] 即可:
    [ApiVersionNeutral][Route("api/health")]public class HealthController : ControllerBase{[HttpGet]public IActionResult Get() => Ok("Health OK");}

2.3 客户端代理与 Swagger 分组生成 ⚙️

当启用版本化后,ABP 会自动在 Swagger UI 上按版本分组暴露文档。开发者可以通过以下几种方式生成客户端代理:

2.3.1 手动执行命令
   abp suite generate-proxy

该命令会根据当前已发布的 Swagger 文档(包括多个版本)在 *.HttpApi.Client 项目中生成对应的 TypeScript/C# 代理文件,并自动分文件夹存放。📁


2.3.2 自动生成配置

如果希望每次项目启动时自动生成,可在 YourProjectHttpApiHostModule 中添加以下配置,并确保已启用 Swagger 生成配置(见 2.1 中的 AddSwaggerGen):

   using Volo.Abp.AspNetCore.Mvc.ApiExplorer;public override void ConfigureServices(ServiceConfigurationContext context){// ... 上述版本化和 Swagger 配置 ...Configure<AbpApiDescriptionModelOptions>(options =>{options.IsControllerModelEnabled = true;});}

注意

  • 仅在你想要每次运行项目时自动生成 TypeScript/C# 代理时配置 IsControllerModelEnabled = true。若只想手动触发生成,可忽略此配置。
  • 为了让 Swagger UI 正常显示分组,需要在 AddSwaggerGen 中编写 DocInclusionPredicateOperationFilterDocumentFilter 等逻辑,详见 2.1。

2.3.3 Swagger 文档生成流程图 📊
不是
扫描 Controller 特性
是否 ApiVersionAttribute?
收集版本列表
是否 ApiVersionNeutral?
包含在所有文档
忽略该 API
根据版本分组生成 SwaggerDoc
应用 RemoveVersionFromParameter
应用 ReplaceVersionWithExactValueInPath
输出多版本 Swagger JSON

三、实战演示 🎬

下面分别示范 URL SegmentQueryStringHeader 三种模式下的版本化实现方式,并给出调用示例与注意事项。💡


3.1 URL Segment 模式(示例一:同类分支)🔀

using Microsoft.AspNetCore.Mvc;namespace YourProject.HttpApi.Controllers
{// 同时支持 v1.0 和 v2.0[ApiVersion("1.0")][ApiVersion("2.0")][Route("api/v{version:apiVersion}/products")]public class ProductController : ControllerBase{// 仅在 v1.0 时暴露该方法[HttpGet, MapToApiVersion("1.0")]public IActionResult GetV1(){return Ok("Product from v1.0");}// 仅在 v2.0 时暴露该方法[HttpGet, MapToApiVersion("2.0")]public IActionResult GetV2(){return Ok("Product from v2.0");}}
}
  • 启动项目后测试
  GET /api/v1.0/products   → 返回 "Product from v1.0" 🥇GET /api/v2.0/products   → 返回 "Product from v2.0" 🥈

注意:如果同一个 Controller 内既使用 URL Segment 又使用 QueryString,可在请求中同时带两种版本号,例如 /api/v1.0/products?v=2.0,框架会优先按照 UrlSegmentApiVersionReader(v1.0)解析;若想优先使用 QueryString,需将 QueryStringApiVersionReader("v") 放在 Combine 方法第一个参数位置。


3.2 URL Segment 模式(示例二:拆分控制器)✂️

当两个版本逻辑差异较大,或者想将不同版本拆分到独立类时,可编写如下示例:

using Microsoft.AspNetCore.Mvc;namespace YourProject.HttpApi.Controllers
{// v1.0 Controller[ApiVersion("1.0")][Route("api/v{version:apiVersion}/products")]public class ProductV1Controller : ControllerBase{[HttpGet]public IActionResult Get() => Ok("Product from v1.0");}// v2.0 Controller[ApiVersion("2.0")][Route("api/v{version:apiVersion}/products")]public class ProductV2Controller : ControllerBase{[HttpGet]public IActionResult Get() => Ok("Product from v2.0");}
}
  • 测试示例
  GET /api/v1.0/products   → 返回 "Product from v1.0" 🥇GET /api/v2.0/products   → 返回 "Product from v2.0" 🥈

对比说明

  • 同类分支(见 3.1):同一个 Controller 内通过 MapToApiVersion 在方法层面区分版本,代码复用率高,但类文件大小可能增加。
  • 拆分控制器:将各版本逻辑完全隔离到不同类,类名更能明确版本含义,便于后续维护;但若大部分逻辑相同,会导致重复代码。🔍

3.3 QueryString 模式 🔍

如果想将版本号放在 QueryString 中,而不在路由路径里,则需在 2.1 中对 ApiVersionReader.Combine(...) 只保留 QueryStringApiVersionReader("v") 或者把它放到第一个位置。下面示例演示只使用 QueryString:

using Microsoft.AspNetCore.Mvc;namespace YourProject.HttpApi.Controllers
{[ApiVersion("1.0")][ApiVersion("2.0")][Route("api/products")]public class ProductQueryController : ControllerBase{// 默认 v1.0[HttpGet, MapToApiVersion("1.0")]public IActionResult GetV1() => Ok("Product from v1.0");// v2.0[HttpGet, MapToApiVersion("2.0")]public IActionResult GetV2() => Ok("Product from v2.0");}
}
  • 请求示例
  GET /api/products?v=1.0   → 返回 "Product from v1.0" 🎯GET /api/products?v=2.0   → 返回 "Product from v2.0" 🎯
  • 如果客户端既不带 v 参数(因配置了 AssumeDefaultVersionWhenUnspecified = true),也不放在 URL 中,则会默认调用 v1.0。

注意:确保在 AddAbpApiVersioning 中的 ApiVersionReader.Combine(...) 顺序中,将 new QueryStringApiVersionReader("v") 放在首位,否则若同时存在 URL、QueryString,会优先按照 URL Segment 解析。⚠️


3.4 Header 模式 📬

Header 模式适用于不想在 URL 中显式暴露版本号的场景。示例如下:

using Microsoft.AspNetCore.Mvc;namespace YourProject.HttpApi.Controllers
{[ApiVersion("1.0")][ApiVersion("2.0")][Route("api/products")]public class ProductHeaderController : ControllerBase{[HttpGet, MapToApiVersion("1.0")]public IActionResult GetV1() => Ok("Product from v1.0");[HttpGet, MapToApiVersion("2.0")]public IActionResult GetV2() => Ok("Product from v2.0");}
}
  • 请求示例
  GET /api/productsHeader: x-api-version: 1.0   → 返回 "Product from v1.0" 📬GET /api/productsHeader: x-api-version: 2.0   → 返回 "Product from v2.0" 📬
  • 如果既不在 URL,也不在 Header 中指定版本,则会使用默认版本(1.0)。

3.5 三种模式对比表 📋

版本传递方式传递形式优点缺点
URL Segment/api/v{version}/resource路由清晰、便于缓存、SEO 友好路径变化需兼容旧客户端
QueryString/api/resource?v={version}简洁易用、易于测试URL 参数可丢失、不美观
HeaderHeader: x-api-version: {version}版本号不暴露在 URL,更灵活;与 URL 解耦客户端需额外设置 Header,调试不直观

参考链接

  • ABP vNext 官方文档
  • Swagger 分组示例代码

相关文章:

  • Arch安装megaton
  • 湖北理元理律所:企业债务重组中的“法律缓冲带”设计
  • 服务器间文件传输
  • 好用的C/C++/嵌入式 IDE: CLion的下载安装教程(保姆级教程)
  • Python----目标检测(《YOLOv3:AnIncrementalImprovement》和YOLO-V3的原理与网络结构)
  • 5.RV1126-OPENCV 图形计算面积
  • Python----目标检测(《YOLO9000: Better, Faster, Stronger》和YOLO-V2的原理与网络结构)
  • Node.js 项目调试指南
  • 「Java教案」算术运算符与表达式
  • 机器人自动火焰切割H型钢的系统设计与应用
  • 云计算数据治理
  • 机器人夹爪的选型与ROS通讯——机器人抓取系统基础系列(六)
  • 微型导轨在手术机器人领域中有哪些关键操作?
  • Git-flow流
  • BUUCTF[极客大挑战 2019]Secret File 1题解
  • Deepseek给出的8255显示例程
  • 微服务常用日志追踪方案:Sleuth + Zipkin + ELK
  • 【Zephyr 系列 3】多线程与调度机制:让你的 MCU 同时干多件事
  • 【数学 逆序对 构造】P12386 [蓝桥杯 2023 省 Python B] 混乱的数组|普及+
  • 深度剖析:AI 建站的现状、局限与未来展望-AI编程建站实战系列预告优雅草卓伊凡
  • 朝阳网站建设是什么/香港域名注册网站
  • 天津建网站/企业关键词优化推荐
  • 网站行程表怎么做/济南seo顾问
  • 怀远网站建设哪家好/线上推广工作内容
  • 跨境网站建设/网络营销的四个策略
  • 前端开发一般用什么软件/青岛seo优化