Swagger UI 不是 OpenAPI,本质上只是一个展示工具
很多 .NET 开发者习惯把 Swagger UI 和 OpenAPI 混在一起说。文章一开始就强调了一个关键区别:OpenAPI 是规范,Swagger UI 是读取这个规范并展示出来的界面。
OpenAPI 描述 REST API 的路径、参数、请求体、响应、鉴权方式和 schema。它可以被很多工具消费:文档 UI、客户端生成器、测试工具、网关、Mock 服务。Swagger UI 只是其中一个 UI。
因此,从 Swagger UI 换到 Scalar,不是抛弃 OpenAPI,而是换一个更现代的展示和调试界面。
.NET 新模板变化带来的误解
很多人发现新建 .NET Web API 项目后,熟悉的 /swagger 页面不见了,于是以为 OpenAPI 支持被削弱了。实际不是这样。
新的 ASP.NET Core 已经能直接生成 OpenAPI 文档。变化在于:模板不再默认绑定老的 Swagger UI 方案,UI 需要开发者自行选择。
这是一种更清晰的分工:
• ASP.NET Core 负责生成 OpenAPI 文档。 • Scalar、Swagger UI 或其他工具负责展示文档。
这也意味着你可以保留标准文档输出,同时选择更适合团队体验的 UI。
最小 API 中启用 OpenAPI
先看一个简单的 .NET API。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.MapGet("/products/{id:guid}", async (
Guid id,
ProductRepository repository,
CancellationToken cancellationToken) =>
{
var product = await repository.FindAsync(id, cancellationToken);
return product isnull ? Results.NotFound() : Results.Ok(product);
})
.WithName("GetProduct")
.WithSummary("获取商品详情")
.WithDescription("根据商品 ID 查询商品详情。");
app.Run();这段代码的关键是 AddOpenApi() 和 MapOpenApi()。前者注册生成能力,后者暴露 OpenAPI 文档端点。
加上 Scalar UI
Scalar 的价值在于提供更现代的 API 文档界面。它可以读取 OpenAPI 文档,并生成更适合日常调试的页面。
典型配置如下:
using Scalar.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference(options =>
{
options.Title = "商品服务 API";
options.Theme = ScalarTheme.Moon;
options.DefaultHttpClient = new(ScalarTarget.CSharp, ScalarClient.HttpClient);
});
}
app.Run();现在,OpenAPI 文档仍由 ASP.NET Core 生成,Scalar 只负责展示。这样结构更清楚,也更容易替换 UI。
给接口补充更好的文档信息
API 文档好不好,不只取决于 UI,也取决于元数据是否完整。接口命名、摘要、描述、响应类型都应该写清楚。
app.MapPost("/products", async (
CreateProductRequest request,
ProductRepository repository,
CancellationToken cancellationToken) =>
{
var product = Product.Create(
request.Name,
request.Price,
request.Currency);
await repository.AddAsync(product, cancellationToken);
return Results.Created($"/products/{product.Id}", ProductResponse.From(product));
})
.WithName("CreateProduct")
.WithSummary("创建商品")
.WithDescription("创建一个新的商品,并返回创建后的商品信息。")
.Produces<ProductResponse>(StatusCodes.Status201Created)
.ProducesValidationProblem();请求和响应模型也应保持清晰。
public sealed record CreateProductRequest(
string Name,
decimal Price,
string Currency);
public sealed record ProductResponse(
Guid Id,
string Name,
decimal Price,
string Currency)
{
public static ProductResponse From(Product product)
{
return new ProductResponse(
product.Id,
product.Name,
product.Price,
product.Currency);
}
}好的文档 UI 能提升体验,但如果接口元数据很差,再好的 UI 也只是把混乱展示得更漂亮。
生产环境是否应该公开 API 文档
很多项目在开发环境开放 API 文档,在生产环境关闭。这是合理默认值,但不是唯一选择。
如果生产环境需要开放文档,至少要考虑鉴权。
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference();
}
else
{
app.MapOpenApi()
.RequireAuthorization("ApiDocs");
app.MapScalarApiReference()
.RequireAuthorization("ApiDocs");
}还可以用配置开关控制:
var enableApiDocs = app.Configuration.GetValue<bool>("ApiDocs:Enabled");
if (enableApiDocs)
{
app.MapOpenApi();
app.MapScalarApiReference(options =>
{
options.Title = "内部 API 文档";
});
}不要默认把内部接口、调试接口和管理接口暴露给公网。API 文档本身不会执行危险操作,但它会清楚展示系统接口结构。
Swagger UI 是否已经不能用了
文章并不是说 Swagger UI 已经不能用。Swagger UI 仍然是成熟工具,生态广,团队熟悉度高。如果项目已经稳定使用它,也没有必要为了“新”而替换。
Scalar 更适合这些场景:
• 新建 .NET 9/10 API 项目。 • 团队觉得旧 Swagger UI 页面拥挤或过时。 • API 很多,需要更好的浏览和调试体验。 • 希望减少旧 Swashbuckle 依赖。 • 想让文档页面更符合现代开发者工具审美。
如果当前 Swagger UI 运行稳定、团队没有痛点,迁移优先级可以很低。
迁移建议
比较稳的迁移方式是先保留 OpenAPI 输出,再替换 UI。
1. 确认 /openapi/v1.json或等价文档端点可用。2. 补齐接口元数据,包括名称、摘要、响应。 3. 在开发环境加入 Scalar。 4. 让团队试用一段时间。 5. 如果体验满意,再决定是否移除旧 Swagger UI。
不要在同一个 PR 里同时重构 API、升级框架、替换文档工具。这样出了问题很难定位。
一个完整的示例结构
下面是一个简化但比较完整的 Program 配置。
using Scalar.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization();
builder.Services.AddOpenApi();
builder.Services.AddSingleton<ProductRepository>();
var app = builder.Build();
app.UseAuthorization();
app.MapOpenApi();
app.MapScalarApiReference(options =>
{
options.Title = "商品中心 API";
options.Theme = ScalarTheme.DeepSpace;
options.DefaultHttpClient = new(ScalarTarget.CSharp, ScalarClient.HttpClient);
});
app.MapGet("/products/{id:guid}", async (
Guid id,
ProductRepository repository,
CancellationToken cancellationToken) =>
{
var product = await repository.FindAsync(id, cancellationToken);
return product isnull ? Results.NotFound() : Results.Ok(ProductResponse.From(product));
})
.WithName("GetProduct")
.WithSummary("获取商品详情")
.Produces<ProductResponse>()
.Produces(StatusCodes.Status404NotFound);
app.MapPost("/products", async (
CreateProductRequest request,
ProductRepository repository,
CancellationToken cancellationToken) =>
{
var product = Product.Create(request.Name, request.Price, request.Currency);
await repository.AddAsync(product, cancellationToken);
return Results.Created($"/products/{product.Id}", ProductResponse.From(product));
})
.WithName("CreateProduct")
.WithSummary("创建商品")
.Produces<ProductResponse>(StatusCodes.Status201Created)
.ProducesValidationProblem();
app.Run();示例仓储:
public sealedclassProductRepository
{
privatereadonly Dictionary<Guid, Product> _products = new();
public Task<Product?> FindAsync(Guid id, CancellationToken cancellationToken)
{
_products.TryGetValue(id, outvar product);
return Task.FromResult(product);
}
public Task AddAsync(Product product, CancellationToken cancellationToken)
{
_products[product.Id] = product;
return Task.CompletedTask;
}
}总结
这篇文章的重点是把 OpenAPI 和 Swagger UI 分开看。OpenAPI 是标准,仍然应该保留;Swagger UI 只是展示方式之一,可以被 Scalar 替换。
对于 .NET 项目来说,更现代的组合是:框架生成 OpenAPI 文档,Scalar 负责展示和交互。
但迁移的前提是团队确实有体验痛点,并且接口元数据足够清楚。
工具不会自动让 API 变好。好的 API 文档来自清晰的接口设计、完整的元数据和合适的展示界面。