OpenAPI/Swagger 进阶
大约 8 分钟约 2479 字
OpenAPI/Swagger 进阶
简介
在基础 Swagger 配置之上,ASP.NET Core 提供了丰富的 OpenAPI 定制能力,包括自定义文档信息、接口分组、XML 注释、安全定义和代码生成。本篇介绍进阶的 OpenAPI 配置和最佳实践。
特点
多文档分组
按版本分组
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "My API V1",
Version = "v1",
Description = "第一版 API",
Contact = new OpenApiContact
{
Name = "开发团队",
Email = "dev@example.com"
}
});
options.SwaggerDoc("v2", new OpenApiInfo
{
Title = "My API V2",
Version = "v2",
Description = "第二版 API(含新增接口)"
});
// 按特性分配到不同文档
options.DocInclusionPredicate((docName, apiDesc) =>
{
if (!apiDesc.TryGetMethodInfo(out var method)) return false;
var versions = method.DeclaringType?
.GetCustomAttributes(true)
.OfType<ApiVersionAttribute>()
.SelectMany(a => a.Versions)
.Select(v => $"v{v}");
return versions?.Contains(docName) ?? false;
});
});
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "API V1");
options.SwaggerEndpoint("/swagger/v2/swagger.json", "API V2");
options.RoutePrefix = "docs"; // 访问路径: /docs
});XML 注释
自动生成描述
// csproj 中启用 XML 文档
// <PropertyGroup>
// <GenerateDocumentationFile>true</GenerateDocumentationFile>
// </PropertyGroup>
builder.Services.AddSwaggerGen(options =>
{
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
options.IncludeXmlComments(xmlPath);
// 包含控制器 XML 注释
options.EnableAnnotations(); // NuGet: Swashbuckle.AspNetCore.Annotations
});
/// <summary>
/// 产品管理接口
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
/// <summary>
/// 获取所有产品列表
/// </summary>
/// <param name="category">产品分类(可选)</param>
/// <param name="page">页码,从1开始</param>
/// <param name="size">每页数量</param>
/// <returns>产品分页列表</returns>
/// <response code="200">成功返回产品列表</response>
/// <response code="401">未认证</response>
[HttpGet]
[ProducesResponseType(typeof(PagedResult<Product>), 200)]
[ProducesResponseType(401)]
public IActionResult GetAll(
[FromQuery] string? category,
[FromQuery] int page = 1,
[FromQuery] int size = 20)
{
return Ok(new { Items = new List<string>(), Total = 0 });
}
/// <summary>
/// 创建新产品
/// </summary>
/// <param name="request">产品信息</param>
/// <returns>创建的产品</returns>
/// <remarks>
/// 示例请求:
/// POST /api/products
/// {
/// "name": "笔记本电脑",
/// "price": 5999.00,
/// "category": "电子产品"
/// }
/// </remarks>
[HttpPost]
[ProducesResponseType(typeof(Product), 201)]
[ProducesResponseType(400)]
public IActionResult Create([FromBody] CreateProductRequest request)
{
return CreatedAtAction(nameof(GetById), new { id = 1 }, request);
}
/// <summary>
/// 根据ID获取产品
/// </summary>
[HttpGet("{id}")]
public IActionResult GetById(int id) => Ok(new { Id = id });
}安全定义
JWT Bearer 认证
builder.Services.AddSwaggerGen(options =>
{
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header. Example: 'Bearer {token}'",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
// API Key 方式
options.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme
{
Description = "API Key in header",
Name = "X-API-Key",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey
});
});自定义 Schema
枚举和示例
builder.Services.AddSwaggerGen(options =>
{
// 枚举显示为字符串
options.SchemaFilter<EnumSchemaFilter>();
// 添加示例
options.MapType<decimal>(() => new OpenApiSchema
{
Type = "number",
Format = "decimal",
Example = new OpenApiDouble(0.0)
});
});
// 枚举 Schema 过滤器
public class EnumSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (context.Type.IsEnum)
{
schema.Type = "string";
schema.Enum = Enum.GetNames(context.Type)
.Select(name => new OpenApiString(name))
.ToList();
}
}
}
// 请求模型示例
public class CreateProductRequest
{
/// <summary>产品名称</summary>
/// <example>笔记本电脑</example>
[Required, StringLength(100)]
public string Name { get; set; } = "";
/// <summary>价格</summary>
/// <example>5999.00</example>
[Range(0.01, 999999.99)]
public decimal Price { get; set; }
/// <summary>分类</summary>
/// <example>电子产品</example>
public string Category { get; set; } = "";
}Swagger UI 定制
自定义样式
app.UseSwaggerUI(options =>
{
options.DocumentTitle = "My API Documentation";
options.RoutePrefix = "docs";
options.DefaultModelsExpandDepth(-1); // 默认折叠模型
options.DisplayRequestDuration(); // 显示请求耗时
options.EnablePersistAuthorization(); // 持久化认证信息
// 自定义 CSS(注入样式)
options.InjectStylesheet("/swagger-ui/custom.css");
});
// 静态文件中间件
app.UseStaticFiles();操作过滤器与自定义文档
// 自定义 OperationFilter — 为所有接口添加通用参数
public class AuthorizeCheckOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
// 检查接口是否有 [Authorize] 特性
var hasAuthorize = context.MethodInfo.DeclaringType?.GetCustomAttributes(true)
.Any(a => a.GetType() == typeof(AuthorizeAttribute)) ?? false;
if (!hasAuthorize)
hasAuthorize = context.MethodInfo.GetCustomAttributes(true)
.Any(a => a.GetType() == typeof(AuthorizeAttribute));
if (hasAuthorize)
{
operation.Security = new List<OpenApiSecurityRequirement>
{
new()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
}
};
}
// 为所有接口添加响应示例
operation.Responses.TryAdd("500", new OpenApiResponse
{
Description = "服务器内部错误",
Content = new Dictionary<string, OpenApiMediaType>
{
["application/json"] = new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "ProblemDetails" }
}
}
}
});
}
}
// 注册
builder.Services.AddSwaggerGen(options =>
{
options.OperationFilter<AuthorizeCheckOperationFilter>();
});
// 自定义文档过滤器 — 按标签排序
public class TagReorderDocumentFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
var sortedTags = swaggerDoc.Tags
.OrderBy(t => t.Name)
.ToList();
swaggerDoc.Tags.Clear();
foreach (var tag in sortedTags)
swaggerDoc.Tags.Add(tag);
}
}
// 自定义 SchemaFilter — 隐藏内部属性
public class InternalSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (schema.Properties == null) return;
var hiddenProps = context.Type.GetProperties()
.Where(p => p.GetCustomAttributes<JsonIgnoreAttribute>().Any())
.Select(p => char.ToLowerInvariant(p.Name[0]) + p.Name.Substring(1));
foreach (var propName in hiddenProps)
{
schema.Properties.Remove(propName);
}
}
}按模块分组
// 按业务模块分组文档
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("user", new OpenApiInfo
{
Title = "用户管理 API",
Version = "v1",
Description = "用户注册、登录、个人信息管理"
});
options.SwaggerDoc("order", new OpenApiInfo
{
Title = "订单管理 API",
Version = "v1",
Description = "订单创建、查询、支付、退款"
});
options.SwaggerDoc("product", new OpenApiInfo
{
Title = "商品管理 API",
Version = "v1",
Description = "商品列表、搜索、分类管理"
});
// 按标签分组
options.TagActionsBy(api =>
{
if (api.GroupName != null)
return new[] { api.GroupName };
var controllerTags = api.ActionDescriptor.RouteValues["controller"];
return new[] { controllerTags ?? "Default" };
});
options.DocInclusionPredicate((name, api) => true);
});
// SwaggerUI 多文档配置
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/user/swagger.json", "用户管理");
options.SwaggerEndpoint("/swagger/order/swagger.json", "订单管理");
options.SwaggerEndpoint("/swagger/product/swagger.json", "商品管理");
// 多文档切换布局
options.Layout = "StandaloneLayout";
options.DeepLinking = true;
options.DisplayOperationId();
});
// Controller 标记模块
[ApiController]
[Route("api/users")]
[Tags("用户管理")]
public class UsersController : ControllerBase { }
[ApiController]
[Route("api/orders")]
[Tags("订单管理")]
public class OrdersController : ControllerBase { }请求/响应示例与验证
// 使用 SwaggerRequestExample / SwaggerResponseExample(第三方库)
// 或通过 XML 注释 + <remarks> 标签添加示例
/// <summary>
/// 创建订单
/// </summary>
/// <param name="request">订单信息</param>
/// <returns>创建的订单详情</returns>
/// <remarks>
/// 请求示例:
///
/// POST /api/orders
/// {
/// "userId": 1001,
/// "items": [
/// {
/// "productId": 1,
/// "productName": "笔记本电脑",
/// "quantity": 2,
/// "price": 5999.00
/// }
/// ],
/// "remark": "请尽快发货"
/// }
///
/// 响应示例:
///
/// HTTP/1.1 201 Created
/// {
/// "id": 10001,
/// "orderNo": "ORD202401010001",
/// "status": "Pending",
/// "totalAmount": 11998.00
/// }
///
/// 错误响应:
///
/// HTTP/1.1 400 Bad Request
/// {
/// "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
/// "title": "Validation Error",
/// "status": 400,
/// "errors": {
/// "Items[0].Quantity": ["数量必须大于 0"]
/// }
/// }
/// </remarks>
[HttpPost]
[ProducesResponseType(typeof(OrderDto), 201)]
[ProducesResponseType(typeof(ValidationProblemDetails), 400)]
[ProducesResponseType(typeof(ProblemDetails), 500)]
public async Task<IActionResult> CreateOrder([FromBody] CreateOrderRequest request)
{
var order = await _orderService.CreateAsync(request);
return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
}生产环境安全策略
// 生产环境条件性启用 Swagger
var app = builder.Build();
if (!app.Environment.IsProduction())
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.RoutePrefix = "docs";
});
}
else
{
// 生产环境:只在特定路径和授权后可访问
app.UseWhen(
context => context.Request.Path.StartsWithSegments("/docs")
&& context.User.IsInRole("Developer"),
swaggerApp =>
{
swaggerApp.UseSwagger();
swaggerApp.UseSwaggerUI();
});
}
// 或通过中间件保护 Swagger 端点
app.Use(async (context, next) =>
{
if (context.Request.Path.StartsWithSegments("/swagger"))
{
if (app.Environment.IsProduction())
{
context.Response.StatusCode = 404;
return;
}
}
await next();
});
// 注意事项:
// 1. 生产环境不要暴露 Swagger 端点
// 2. OpenAPI JSON 文件包含完整的 API 信息,不应公开
// 3. 如果需要外部开发者文档,使用独立的文档站点
// 4. 定期清理废弃的 API 文档优点
缺点
总结
OpenAPI/Swagger 进阶配置:多文档分组按版本/模块展示,XML 注释自动生成接口描述,安全定义标注 JWT/ApiKey 认证方式。Swagger UI 支持自定义路由前缀、样式和功能开关。枚举用 SchemaFilter 显示为字符串。生产环境建议仅在开发/测试环境启用 Swagger。使用 [ProducesResponseType] 明确标注每种响应类型。
关键知识点
- 先分清这个主题位于请求链路、后台任务链路还是基础设施链路。
- 服务端主题通常不只关心功能正确,还关心稳定性、性能和可观测性。
- 任何框架能力都要结合配置、生命周期、异常传播和外部依赖一起看。
项目落地视角
- 画清请求进入、业务执行、外部调用、日志记录和错误返回的完整路径。
- 为关键链路补齐超时、重试、熔断、追踪和结构化日志。
- 把配置与敏感信息分离,并明确不同环境的差异来源。
常见误区
- 只会堆中间件或组件,不知道它们在链路中的执行顺序。
- 忽略生命周期和线程池、连接池等运行时资源约束。
- 没有监控和测试就对性能或可靠性下结论。
进阶路线
- 继续向运行时行为、可观测性、发布治理和微服务协同深入。
- 把主题和数据库、缓存、消息队列、认证授权联动起来理解。
- 沉淀团队级模板,包括统一异常处理、配置约定和基础设施封装。
适用场景
- 当你准备把《OpenAPI/Swagger 进阶》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合 API 服务、后台任务、实时通信、认证授权和微服务协作场景。
- 当需求开始涉及稳定性、性能、可观测性和发布流程时,这类主题会成为基础设施能力。
落地建议
- 先定义请求链路与失败路径,再决定中间件、过滤器、服务边界和依赖方式。
- 为关键链路补日志、指标、追踪、超时与重试策略。
- 环境配置与敏感信息分离,避免把生产参数写死在代码或镜像里。
排错清单
- 先确认问题发生在路由、模型绑定、中间件、业务层还是基础设施层。
- 检查 DI 生命周期、配置来源、序列化规则和认证上下文。
- 查看线程池、连接池、缓存命中率和外部依赖超时。
复盘问题
- 如果把《OpenAPI/Swagger 进阶》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《OpenAPI/Swagger 进阶》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《OpenAPI/Swagger 进阶》最大的收益和代价分别是什么?
