Scalar.AspNetCore.Swashbuckle 深度指南:用 OpenAPI Filters 为 Swashbuckle 文档注入 Scalar 扩展
【免费下载链接】scalarScalar is an open-source API platform: 🌐 Modern REST API Client 📖 Beautiful API References ✨ 1st-Class OpenAPI/Swagger Support项目地址: https://gitcode.com/GitHub_Trending/sc/scalar
Scalar.AspNetCore.Swashbuckle是 Scalar 开源 API 平台中面向 .NET 生态的官方集成包,它的核心职责是为Swashbuckle.AspNetCore.SwaggerGen提供一组 OpenAPI Filters,把 Scalar 特有的扩展(稳定性标记、徽章、代码示例、端点隐藏)写入你生成的 OpenAPI 文档中。读完本文,你将掌握该包的安装与注册方式、全部扩展能力的用法(Minimal API 与 Controller 两种写法)、底层过滤器的实现原理,以及如何用仓库内现成的测试用例验证输出结果。
包定位:在不改动业务代码的前提下"增强" OpenAPI 文档
Scalar 本身是一个开源的 API 平台,既提供现代化的 REST API 客户端,也提供漂亮的 API References 界面,并对 OpenAPI/Swagger 有一流的支持。当你在 ASP.NET Core 应用里使用 Swashbuckle 生成 OpenAPI 文档、再用 Scalar.AspNetCore 渲染 Scalar 的 API Reference 界面时,往往会希望文档中携带更多面向阅读者的语义信息——比如某个接口是 experimental 还是 stable、某个接口要打上 "New" 或 "Beta" 徽章、某个内部接口不要出现在参考文档里。
这些需求正是Scalar.AspNetCore.Swashbuckle包的用武之地。其 README 对包定位的描述非常直接:
The
Scalar.AspNetCore.Swashbucklepackage provides OpenAPI filters forSwashbuckle.AspNetCore.SwaggerGenthat enable Scalar-specific extensions in your OpenAPI document.
也就是说,该包不改变你的 API 实现,而是通过 Swashbuckle 的 Filter 机制,在文档生成阶段把 Scalar 扩展写入 OpenAPI 文档。从 Scalar.AspNetCore.Swashbuckle.csproj 可以看到它的依赖面很干净:Microsoft.OpenApi、Swashbuckle.AspNetCore.SwaggerGen,以及项目内引用Scalar.AspNetCore(扩展属性与序列化辅助都来自该包)。目标框架为net8.0;net9.0;net10.0,适用于当前的 .NET 8/9/10 应用。
安装与注册:一条扩展方法接入全部 Filters
首先通过 NuGet 安装包(对应官方文档 openapi-extensions.md 中的说明):
dotnet add package Scalar.AspNetCore.Swashbuckle然后在 OpenAPI 注册阶段调用AddScalarFilters()扩展方法:
using Scalar.AspNetCore; var builder = WebApplication.CreateBuilder(args); // Swashbuckle.AspNetCore.SwaggerGen builder.Services.AddSwaggerGen(options => options.AddScalarFilters()); var app = builder.Build(); app.MapOpenApi(); // 或 app.UseSwagger() 等 Swashbuckle 的文档暴露方式 app.Run();AddScalarFilters的实现位于 SwaggerGenOptionsExtensions.cs,一次调用共注册了 1 个 DocumentFilter 和 4 个 OperationFilter:
public static SwaggerGenOptions AddScalarFilters(this SwaggerGenOptions options) { options.DocumentFilter<ExcludeFromApiReferenceDocumentFilter>(); options.OperationFilter<ExcludeFromApiReferenceOperationFilter>(); options.OperationFilter<StabilityOpenApiOperationFilter>(); options.OperationFilter<CodeSampleOperationFilter>(); options.OperationFilter<BadgeOperationFilter>(); return options; }各过滤器与对应扩展键的对应关系如下:
| 过滤器 | 类型 | 写入的扩展键 | 作用 |
|---|---|---|---|
StabilityOpenApiOperationFilter | OperationFilter | x-scalar-stability | 标记接口稳定性(stable / experimental / deprecated) |
BadgeOperationFilter | OperationFilter | x-badges | 为操作添加视觉徽章 |
CodeSampleOperationFilter | OperationFilter | x-codeSamples | 为操作添加自定义代码示例 |
ExcludeFromApiReferenceOperationFilter | OperationFilter | x-scalar-ignore | 标记单个操作不出现在 API Reference |
ExcludeFromApiReferenceDocumentFilter | DocumentFilter | x-scalar-ignore(提升到 tag 级) | 整组隐藏某个 tag 下的全部操作 |
这些扩展键的常量定义集中在 ExtensionKeys.cs(ScalarIgnore、ScalarStability、CodeSamples、Badges),并通过 JsonSerializerHelper.cs 中的源生成序列化上下文,以 camelCase 命名、忽略 null 值的方式写入 JSON。
标记 API 稳定性:x-scalar-stability
稳定性信息帮助使用者判断一个接口是否适合投入生产。Scalar 定义了三个稳定性级别(见 Stability.cs 与官方文档):
Stable:生产就绪的 API,序列化为"stable"Experimental:可能随时变更、不建议生产使用的 API,序列化为"experimental"Deprecated:将在未来版本移除的 API,序列化为"deprecated"
Minimal API 写法
app.MapGet("/products", GetProducts).Stable(); app.MapGet("/beta-features", GetBetaFeatures).Experimental(); app.MapGet("/legacy-endpoint", GetLegacyData).Deprecated();Controller 写法
[HttpGet] [Stability(Stability.Stable)] public IActionResult GetProducts() => Ok();Minimal API 一侧的Stable()/Experimental()/Deprecated()扩展方法定义在 EndpointConventionBuilderExtensions.cs,它们内部都通过WithStability往 EndpointMetadata 中写入StabilityAttribute(定义见 StabilityAttribute.cs)。
底层过滤器 StabilityOpenApiOperationFilter.cs 的读取逻辑值得注意:
// We use LastOrDefault because this allows a specific endpoint to override the stability var stabilityAttribute = context.ApiDescription.ActionDescriptor.EndpointMetadata .OfType<StabilityAttribute>() .LastOrDefault();也就是说,当你在分组(MapGroup)级别标记了稳定性、又在具体端点上再次标记时,LastOrDefault保证端点级标记覆盖分组级标记。命中后过滤器把稳定性值序列化为x-scalar-stability扩展:
{ "x-scalar-stability": "experimental" }这一输出被 StabilityFilterTests.cs 以 JSON 全量比对的方式验证,测试覆盖了stable、experimental、deprecated三种取值。
从 API Reference 中隐藏端点:x-scalar-ignore
有些内部端点你希望保留在 OpenAPI 文档(供工具调用)中,但不想让它在 Scalar 的 API Reference 界面里出现。ExcludeFromApiReference正好解决这个需求,且端点仍然可以正常访问。
Minimal API 写法
app.MapGet("/internal/metrics", GetMetrics).ExcludeFromApiReference();Controller 写法
[HttpGet] [ExcludeFromApiReference] public IActionResult GetInternalMetrics() => Ok();隐藏逻辑由两个过滤器协作完成:
- ExcludeFromApiReferenceOperationFilter.cs 是 OperationFilter,检测到
ExcludeFromApiReferenceAttribute后给该操作加上x-scalar-ignore: true; - ExcludeFromApiReferenceDocumentFilter.cs 是 DocumentFilter,它先把文档中所有操作按 tag 分组,然后找出"该 tag 下全部操作都带
x-scalar-ignore"的 tag,把忽略标记提升到 tag 级别,并移除操作上的冗余标记:
var tagsToExclude = tagOperations.Where(kvp => kvp.Value.All(operation => operation.Extensions is not null && operation.Extensions.ContainsKey(ScalarIgnore)));这样处理的好处是:Scalar 渲染时只需要识别 tag 级标记即可整组隐藏,文档结构也更干净。注意官方文档明确提示——被隐藏的端点仍然可以通过 API 访问,只是不会出现在 API Reference 界面中。
添加自定义代码示例:x-codeSamples
默认情况下,Scalar 的 API Reference 会为每个操作自动生成多种语言的代码示例。如果你希望针对某个端点提供手写示例(例如展示特定的鉴权头、特定的调用方式),可以用CodeSample覆盖或补充。
Minimal API 写法
app.MapPost("/orders", CreateOrder) .CodeSample("fetch('/orders', { method: 'POST', body: JSON.stringify(order) })", ScalarTarget.JavaScript, "Create Order") .CodeSample("curl -X POST /orders -d @order.json", ScalarTarget.Shell, "Create with cURL");Controller 写法
[HttpGet] [CodeSample("fetch('/products').then(r => r.json())", ScalarTarget.JavaScript)] public IActionResult GetProducts() => Ok();CodeSampleAttribute(见 CodeSampleAttribute.cs)有三个构造参数:sample(示例代码内容)、language(ScalarTarget枚举,如ScalarTarget.CSharp、ScalarTarget.JavaScript、ScalarTarget.Shell)、label(可选标签),并且AllowMultiple = true,一个端点可以挂多个示例。Minimal API 侧对应的扩展方法是 EndpointConventionBuilderExtensions.cs 中的CodeSample。
CodeSampleOperationFilter.cs 会把端点元数据中的所有CodeSampleAttribute批量收集为CodeSample数据模型,写入x-codeSamples扩展:
{ "x-codeSamples": [ { "source": "fetch('/orders', { method: 'POST', body: JSON.stringify(order) })", "label": "Create Order", "language": "javascript" } ] }添加视觉徽章:x-badges
徽章用于在 API Reference 界面上给操作附加醒目的视觉标识,比如 "New"、"Beta"、"Internal"。每个操作可以挂多个徽章,并分别配置位置与颜色。
Minimal API 写法
app.MapGet("/alpha-feature", GetAlphaFeature) .WithBadge("Alpha") .WithBadge("Beta", BadgePosition.Before) .WithBadge("Internal", BadgePosition.After, "#ff6b35"); app.MapPost("/orders", CreateOrder) .WithBadge("New", color: "#28a745") .WithBadge("Premium", BadgePosition.Before, "#ffc107");Controller 写法
[HttpGet] [Badge("New")] [Badge("V2", BadgePosition.After, "#007bff")] public IActionResult GetExperimentalFeature() => Ok();徽章的可配置项(官方文档 openapi-extensions.md 与 BadgeAttribute.cs 均有说明):
| 参数 | 必填 | 说明 |
|---|---|---|
name | 是 | 徽章上显示的文本 |
position | 否 | 徽章相对操作标题的位置,BadgePosition.After(默认)/BadgePosition.Before |
color | 否 | 徽章颜色,支持任意 CSS 颜色格式(hex、rgb、颜色关键字等) |
BadgePosition枚举定义在 BadgePosition.cs,序列化时分别输出为"after"/"before"。
BadgeOperationFilter.cs 会收集端点上的全部BadgeAttribute(同样AllowMultiple = true)写入x-badges扩展。仓库测试 BadgeFilterTests.cs 给出了非常直观的期望输出——当分组级挂了 "Alpha" 徽章、端点级又挂了 "Beta"(before)、"Gamma"(after + 颜色)、"Delta"(仅颜色)时,生成的 JSON 为:
"x-badges": [ { "name": "Alpha" }, { "name": "Beta", "position": "before" }, { "name": "Gamma", "position": "after", "color": "#ffcc00" }, { "name": "Delta", "color": "#00ff00" } ]可见分组级徽章会自动继承到组内每个操作上,这也是为什么一个操作可能同时包含来自分组和自身两部分的徽章。
已弃用端点的处理
除了x-scalar-stability: "deprecated"这种标记方式,仓库中还提供了一个专门处理DeprecatedAttribute的过滤器 DeprecatedEndpointFilter.cs。它读取Scalar.AspNetCore.Attributes.DeprecatedAttribute(见 DeprecatedAttribute.cs,可传入可选参数reason说明弃用原因或新接口指引),然后:
- 将
operation.Deprecated置为true(对应 OpenAPI 标准的deprecated字段); - 若提供了
reason,则把它追加到操作的Description中。
需要说明的是:从源码结构看,AddScalarFilters当前并未注册该过滤器,仓库中已注册的是稳定性、徽章、代码示例和隐藏端点这五个过滤器。如果你希望启用"原因描述"式的弃用标注,可以按需自行通过options.OperationFilter<...>()注册,或优先使用[Stability(Stability.Deprecated)]/.Deprecated()的稳定性路径。
底层实现剖析:一套统一的元数据读取模式
把五个过滤器放在一起看,会发现它们遵循完全一致的实现模式,这也是本包易于理解和扩展的原因:
- 从 EndpointMetadata 读取特性:所有过滤器都通过
context.ApiDescription.ActionDescriptor.EndpointMetadata.OfType<TAttribute>()从端点元数据中取出对应特性——Minimal API 的.WithBadge(...)/.CodeSample(...)扩展方法最终调用builder.WithMetadata(new XxxAttribute(...))(见 EndpointConventionBuilderExtensions.cs),Controller 则直接使用[Xxx]特性,两条路径殊途同归,最终都落在 EndpointMetadata 上; - 按需初始化扩展集合:
operation.Extensions ??= new Dictionary<string, IOpenApiExtension>();,确保不覆盖已有扩展; - 幂等写入:使用
Extensions.TryAdd(...)避免重复注入同一个扩展键; - 统一序列化:JsonSerializerHelper.cs 使用
System.Text.Json的源生成上下文(ScalarExtensionsSerializerContext)序列化CodeSample、Badge、Stability等模型,并统一配置 camelCase 命名与 null 值忽略,保证输出的扩展 JSON 风格一致。
这种"特性(Attribute)声明 + Filter 消费"的设计,让所有扩展能力都可以同时工作在 Minimal API、Controller 与最小化 API 的各种托管模型上,且不需要修改任何业务实现代码。
测试与验证:仓库如何保证扩展输出正确
Scalar.AspNetCore.Swashbuckle的每个过滤器都有对应的集成测试,位于 integrations/dotnet/aspnetcore/tests/Scalar.AspNetCore.Swashbuckle.Tests,包括:
- BadgeFilterTests.cs:验证
x-badges的完整 JSON 结构; - StabilityFilterTests.cs:验证
x-scalar-stability三种取值; CodeSampleFilterTests.cs:验证x-codeSamples的注入;ExcludeFromApiReferenceFilterTests.cs:验证x-scalar-ignore的隐藏逻辑。
测试的写法很有参考价值:它们通过WebApplicationFactory+ConfigureTestServices注册AddScalarFilters,动态搭建路由(含MapSwagger、MapGroup、端点级与分组级的特性组合),再请求生成的 OpenAPI JSON 与期望输出做全量匹配。如果你在自己的项目中接入本包,完全可以把这些测试当作行为契约,快速确认扩展输出的精确格式。
快速上手参考
一个完整的 Minimal API 接入示例(综合了分组级与端点级扩展,参考仓库 playground 的 BookEndpoints.cs 与 Program.cs):
using Scalar.AspNetCore; var builder = WebApplication.CreateBuilder(args); builder.Services.AddSwaggerGen(options => options.AddScalarFilters()); builder.Services.AddOpenApi(); var app = builder.Build(); app.MapOpenApi(); app.MapScalarApiReference(); // 挂载 Scalar API Reference 界面 var books = app.MapGroup("/books").WithTags("bookstore"); books.MapGet("/", () => Results.Ok()).Stable(); books.MapGet("/{id}", (Guid id) => Results.Ok()).Experimental() .WithBadge("Beta", BadgePosition.Before, "#ffcc00"); books.MapDelete("/{id}", (Guid id) => Results.NoContent()) .WithBadge("Caution", BadgePosition.Before, "#ffc2c2"); app.Run();至此,你的 Swashbuckle 文档中就会自动携带x-scalar-stability、x-badges、x-codeSamples、x-scalar-ignore等 Scalar 扩展,Scalar API Reference 界面将据此渲染出带稳定性标识、徽章、自定义代码示例且过滤掉内部端点的文档体验。想要进一步查阅各扩展的完整能力与属性说明,可以对照 openapi-extensions.md 文档,并结合本文提到的过滤器源码逐一验证。
【免费下载链接】scalarScalar is an open-source API platform: 🌐 Modern REST API Client 📖 Beautiful API References ✨ 1st-Class OpenAPI/Swagger Support项目地址: https://gitcode.com/GitHub_Trending/sc/scalar
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考