你知道吗?从 .NET9 开始删除内置的 Swagger 支持 (Swashbuckle)!

.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),原因如下:

  1. 维护问题:Swashbuckle 项目不再由其社区所有者积极维护。问题未得到解决或解决,并且 .NET 8 没有正式版本。
  2. ASP.NET Core 的演变:自从在 .NET 5 中引入 Swagger 支持以来,ASP.NET Core 已经有了显著的发展。它现在内置了对描述 Web API 所需的元数据的支持,从而减少了对外部工具的需求。
  3. 专注于 OpenAPI:该团队希望使 OpenAPI 成为 ASP.NET Core 中的一等公民。他们计划扩展 OpenAPI 文档生成功能,而不依赖外部包。Microsoft.AspNetCore.OpenApi
  4. 替代工具:Visual Studio 现在提供对 .http 文件的内置支持和新的 Endpoints Explorer,从而提供探索、测试和调试 API 的替代方法。
  5. 社区驱动的创新:通过消除默认依赖项,团队鼓励使用和开发可能更适合特定项目需求的各种 OpenAPI 工具。

Swagger 的替代方案:Scalar(Scalar.AspNetCore)

Scalar 是来自 OpenAPI/Swagger 文档的交互式 API 文档。

这个 .NETScalar.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 类。

也可以使用 options 模式通过依赖注入来配置选项:

csharp 复制代码
builder.Services.Configure<ScalarOptions>(options => options.Title = "My custom API");
// or
builder.Services.AddOptions<ScalarOptions>().BindConfiguration("Scalar");

注意:通过该方法设置的选项将优先于通过依赖关系注入设置的选项。MapScalarApiReference

相关推荐
白总Server13 小时前
孤岛架构与微服务架构区别
java·开发语言·后端·微服务·云原生·架构·scala
睎zyl2 天前
Scala的特质和trait方法
开发语言·后端·scala
莫等闲 白了少年头2 天前
尚硅谷scala
java·开发语言·scala
白总Server3 天前
孤岛架构在异构性方面优势
开发语言·数据库·后端·微服务·架构·scala·1024程序员节
阿尔法波4 天前
Linux基础环境搭建(CentOS7)- 安装Scala和Spark
linux·spark·scala
睎zyl5 天前
Scala的多态概述
开发语言·后端·scala
461K.6 天前
scala trait多继承知识
大数据·开发语言·笔记·后端·scala·1024程序员节
2401_871290587 天前
Scala的内部对象
开发语言·后端·scala
2401_cf7 天前
Scala内部类的运用
开发语言·后端·scala