.NET 9 中使用 Scalar 替代内置的 Swagger 支持 (Swashbuckle)
- [为什么 Swagger (Swashbuckle) 被删除?](#为什么 Swagger (Swashbuckle) 被删除?)
- [Swagger 的替代方案:Scalar(Scalar.AspNetCore)](#Swagger 的替代方案:Scalar(Scalar.AspNetCore))
- [如何在 Dotnet 9 中使用它?](#如何在 Dotnet 9 中使用它?)
- [如何将 Bearer 身份验证方案添加到 Scalar ?](#如何将 Bearer 身份验证方案添加到 Scalar ?)
- [使用 Scalar 的式例](#使用 Scalar 的式例)
-
- [创建 webapi 项目](#创建 webapi 项目)
- [安装 nuget 包](#安装 nuget 包)
- 添加文件夹目录
- [修改 Program.cs 文件](#修改 Program.cs 文件)
- [Scalar 的配置扩展](#Scalar 的配置扩展)
Microsoft
已决定从.NET 9
中删除内置的Swagger
支持 (Swashbuckle
)。
为什么 Swagger (Swashbuckle) 被删除?
ASP.NET Core
团队已决定从 .NET 9
中删除内置的 Swagger
支持 (Swashbuckle
),原因如下:
- 维护问题:
Swashbuckle
项目不再由其社区所有者积极维护。问题未得到解决或解决,并且.NET 8
没有正式版本。 ASP.NET Core
的演变:自从在.NET 5
中引入Swagger
支持以来,ASP.NET Core
已经有了显著的发展。它现在内置了对描述Web API
所需的元数据的支持,从而减少了对外部工具的需求。- 专注于
OpenAPI
:该团队希望使OpenAPI
成为ASP.NET Core
中的一等公民。他们计划扩展OpenAPI
文档生成功能,而不依赖外部包。Microsoft.AspNetCore.OpenApi
- 替代工具:
Visual Studio
现在提供对.http
文件的内置支持和新的Endpoints Explorer
,从而提供探索、测试和调试API
的替代方法。 - 社区驱动的创新:通过消除默认依赖项,团队鼓励使用和开发可能更适合特定项目需求的各种
OpenAPI
工具。
Swagger 的替代方案:Scalar(Scalar.AspNetCore)
Scalar
是来自 OpenAPI/Swagger
文档的交互式 API
文档。
这个
.NET
包Scalar.AspNetCore
提供了一种基于OpenAPI/Swagger
文档呈现漂亮的API
引用的简单方法。
您可以在此处获取更多信息。
如何在 Dotnet 9 中使用它?
1、安装 nuget 包
csharp
dotnet add package Scalar.AspNetCore
2、示例用法
csharp
using Scalar.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapScalarApiReference(); // scalar/v1
app.MapOpenApi();
}
app.MapGet("/", () => "Hello world!");
app.Run();
运行应用程序时,您可以在终端节点访问 API
文档。
bash
http://localhost:port/scalar/v1
如何将 Bearer 身份验证方案添加到 Scalar ?
- 以下是
Bearer
身份验证的示例转换器:
csharp
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi.Models;
namespace WebApplication1.Transformers;
public sealed class BearerSecuritySchemeTransformer(IAuthenticationSchemeProvider authenticationSchemeProvider) : IOpenApiDocumentTransformer
{
private readonly string _authenticationSchemeName = "Bearer";
public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
{
var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync();
if (authenticationSchemes.Any(authScheme => authScheme.Name == _authenticationSchemeName))
{
// Add the security scheme at the document level
var requirements = new Dictionary<string, OpenApiSecurityScheme>
{
[_authenticationSchemeName] = new OpenApiSecurityScheme
{
Type = SecuritySchemeType.Http,
Scheme = _authenticationSchemeName.ToLower(), // "bearer" refers to the header name here
In = ParameterLocation.Header,
BearerFormat = "Json Web Token"
}
};
document.Components ??= new OpenApiComponents();
document.Components.SecuritySchemes = requirements;
// Apply it as a requirement for all operations
foreach (var operation in document.Paths.Values.SelectMany(path => path.Operations))
{
operation.Value.Security.Add(new OpenApiSecurityRequirement
{
[new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Id = _authenticationSchemeName,
Type = ReferenceType.SecurityScheme
}
}] = Array.Empty<string>()
});
}
}
}
}
- 用法示例:
csharp
builder.Services.AddOpenApi(opt =>
{
opt.AddDocumentTransformer<BearerSecuritySchemeTransformer>();
});
使用 Scalar 的式例
创建 webapi 项目
此处我们使用 .NET CLI
创建 ASP.NET Core Web API
项目,操作步骤如下:
1、确保已安装 .NET SDK
:
- 可以通过运行
dotnet --version
命令来检查是否已安装.NET9 SDK
。 - 如果未安装,可以从
.NET
官方网站 下载并安装。
.NET
官方网站 :
2、创建项目目录:
bash
mkdir MyWebApi
3、创建 ASP.NET Core Web API
项目:
bash
dotnet new webapi -n MyWebApi
4、导航到项目目录:
bash
cd MyWebApi
5、运行项目:
bash
dotnet run
通过上面操作,此时我们就准备好 asp.net core webapi
项目,名称为:MyWebApi
。
安装 nuget 包
使用 .net cli
安装 nuget
包文件,执行如下命令:
bash
dotnet add package Microsoft.AspNetCore.OpenApi --version 9.0.0-rc.2.24474.3
dotnet add package Scalar.AspNetCore --version 1.2.23
添加文件夹目录
在项目中添加文件夹目录,命名为:Transformers
,并在该目录中添加 BearerSecuritySchemeTransformer.cs
文件,该文件的作用是将 Bearer 身份验证方案添加到 Scalar 中,代码演示如签名环节说明。
修改 Program.cs 文件
此处使用 MiniAPI
模式,在 Program.cs
文件中代码改造如下:
csharp
using System.Text.Json.Serialization;
using Scalar.AspNetCore;
using WebApplication1.Transformers;
var builder = WebApplication.CreateSlimBuilder(args);
// 配置 HTTP JSON 选项,用于自定义 JSON 序列化行为。
builder.Services.ConfigureHttpJsonOptions(options =>
{
// 在类型解析链中插入自定义的 JSON 序列化上下文。
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});
// 启用 OpenAPI 文档支持,并配置 OpenAPI 文档。
//builder.Services.AddOpenApi();
builder.Services.AddOpenApi(opt =>
{
// 添加一个文档转换器,用于处理特定的安全方案(例如: Bearer Token)。
opt.AddDocumentTransformer<BearerSecuritySchemeTransformer>();
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
// 映射 OpenAPI 文档路由。
app.MapOpenApi();
//app.MapScalarApiReference(); // scalar/v1
// 映射自定义的 API 文档路由,并使用 Fluent API 进行配置,例如:设置标题、主题、侧边栏等。
app.MapScalarApiReference(options =>
{
options
.WithTitle("My custom API")
.WithTheme(ScalarTheme.Mars)
.WithSidebar(true)
.WithDefaultHttpClient(ScalarTarget.CSharp, ScalarClient.HttpClient)
.WithPreferredScheme("ApiKey")
.WithApiKeyAuthentication(x => x.Token = "my-api-key");
});
}
// 定义示例数据,一个包含示例待办事项的数组
var sampleTodos = new Todo[] {
new(1, "Walk the dog"),
new(2, "Do the dishes", DateOnly.FromDateTime(DateTime.Now)),
new(3, "Do the laundry", DateOnly.FromDateTime(DateTime.Now.AddDays(1))),
new(4, "Clean the bathroom"),
new(5, "Clean the car", DateOnly.FromDateTime(DateTime.Now.AddDays(2)))
};
// 定义 API 路由
var todosApi = app.MapGroup("/todos"); // 创建一个路由组,前缀为 /todos。
todosApi.MapGet("/", () => sampleTodos); // 映射 GET 请求到根路径,返回所有待办事项。
// 映射 GET 请求到特定 ID 的路径,返回对应的待办事项,如果找不到则返回 404 Not Found。
todosApi.MapGet("/{id}", (int id) =>
sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo
? Results.Ok(todo)
: Results.NotFound());
app.Run();
// 定义一个记录类 Todo,包含 Id、Title、DueBy 和 IsComplete 属性。
public record Todo(int Id, string? Title, DateOnly? DueBy = null, bool IsComplete = false);
// 自定义的 JSON 序列化上下文
[JsonSerializable(typeof(Todo[]))] // 标记 AppJsonSerializerContext 类,使其支持 Todo 数组的 JSON 序列化。
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
// 定义一个部分类 AppJsonSerializerContext,继承自 JsonSerializerContext,用于自定义 JSON 序列化行为。
}
运行项目,浏览器输入地址:
- 查看
todos
接口数据
bash
http://localhost:5264/todos
- 查看
scalar
页面:
bash
http://localhost:5264/scalar/v1
- /todos
点击 【test request】显示如下:
点击【send】显示如下:
Scalar 的配置扩展
在 Scalar.AspNetCore
包中,IEndpointRouteBuilder
该方法有一个可选参数,可用于自定义 Scalar UI
的行为:MapScalarApiReference
csharp
#region 程序集 Scalar.AspNetCore, Version=1.2.23.0, Culture=neutral, PublicKeyToken=null
// C:\Users\Jeffrey\.nuget\packages\scalar.aspnetcore\1.2.23\lib\net9.0\Scalar.AspNetCore.dll
// Decompiled with ICSharpCode.Decompiler 8.1.1.7464
#endregion
using System;
using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Generated;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
namespace Scalar.AspNetCore;
//
// 摘要:
// Extension methods for Microsoft.AspNetCore.Routing.IEndpointRouteBuilder to provide
// required endpoints.
public static class ScalarEndpointRouteBuilderExtensions
{
private const string DocumentName = "{documentName}";
private const string StaticAssets = "ScalarStaticAssets";
internal const string ScalarJavaScriptFile = "scalar.js";
//
// 摘要:
// Maps the Scalar API reference endpoint.
//
// 参数:
// endpoints:
// Microsoft.AspNetCore.Routing.IEndpointRouteBuilder.
public static IEndpointConventionBuilder MapScalarApiReference(this IEndpointRouteBuilder endpoints)
{
return endpoints.MapScalarApiReference(delegate
{
});
}
//
// 摘要:
// Maps the Scalar API reference endpoint.
//
// 参数:
// endpoints:
// Microsoft.AspNetCore.Routing.IEndpointRouteBuilder.
//
// configureOptions:
// An action to configure the Scalar options.
public static IEndpointConventionBuilder MapScalarApiReference(this IEndpointRouteBuilder endpoints, Action<ScalarOptions> configureOptions)
{
ScalarOptions options = endpoints.ServiceProvider.GetService<IOptions<ScalarOptions>>()?.Value ?? new ScalarOptions();
configureOptions(options);
if (!options.EndpointPathPrefix.Contains("{documentName}"))
{
throw new ArgumentException("'EndpointPathPrefix' must define '{documentName}'.");
}
bool useLocalAssets = string.IsNullOrEmpty(options.CdnUrl);
string standaloneResourceUrl = (useLocalAssets ? options.EndpointPathPrefix.Replace("{documentName}", "scalar.js") : options.CdnUrl);
EmbeddedFileProvider fileProvider = new EmbeddedFileProvider(typeof(ScalarEndpointRouteBuilderExtensions).Assembly, "ScalarStaticAssets");
FileExtensionContentTypeProvider fileExtensionContentTypeProvider = new FileExtensionContentTypeProvider();
string configuration = JsonSerializer.Serialize(options.ToScalarConfiguration(), ScalaConfigurationSerializerContext.Default.ScalarConfiguration);
return endpoints.MapGet0(options.EndpointPathPrefix, (Func<string, IResult>)delegate (string documentName)
{
if (useLocalAssets)
{
IFileInfo fileInfo = fileProvider.GetFileInfo(documentName);
if (fileInfo.Exists)
{
string contentType;
string contentType2 = (fileExtensionContentTypeProvider.TryGetContentType(documentName, out contentType) ? contentType : "application/octet-stream");
return Results.Stream(fileInfo.CreateReadStream(), contentType2, null, fileInfo.LastModified);
}
}
string value = options.Title.Replace("{documentName}", documentName);
string value2 = options.OpenApiRoutePattern.Replace("{documentName}", documentName);
return Results.Content($"<!doctype html>\n<html>\n<head>\n <title>{value}</title>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n</head>\n<body>\n <script id=\"api-reference\" data-url=\"{value2}\"></script>\n <script>\n document.getElementById('api-reference').dataset.configuration = JSON.stringify({configuration})\n </script>\n <script src=\"{standaloneResourceUrl}\"></script>\n</body>\n</html>", "text/html");
}).ExcludeFromDescription();
}
}
MapScalarApiReference
方法,除了上面的式例中使用的 Fluent API
模式,还可以使用 Object initializer
方式:
csharp
// Object initializer
app.MapScalarApiReference(options =>
{
options.Title = "My custom API";
options.Theme = ScalarTheme.Mars;
options.ShowSidebar = false;
options.DefaultHttpClient = new(ScalarTarget.CSharp, ScalarClient.HttpClient);
options.Authentication = new ScalarAuthenticationOptions
{
PreferredSecurityScheme = "ApiKey",
ApiKey = new ApiKeyOptions
{
Token = "my-api-key"
}
};
});
有关更多可能的选项及其默认值,请查看 ScalarOptions.cs
类。
ScalarOptions.cs
类,https://github.com/scalar/scalar/blob/main/packages/scalar.aspnetcore/src/Scalar.AspNetCore/Options/ScalarOptions.cs
也可以使用 options
模式通过依赖注入来配置选项:
csharp
builder.Services.Configure<ScalarOptions>(options => options.Title = "My custom API");
// or
builder.Services.AddOptions<ScalarOptions>().BindConfiguration("Scalar");
注意:通过该方法设置的选项将优先于通过依赖关系注入设置的选项。
MapScalarApiReference