你知道吗?从 .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

相关推荐
进击的雷神10 小时前
Perl语言深度考查:从文本处理到正则表达式的全面掌握
开发语言·后端·scala
进击的雷神10 小时前
Perl测试起步:从零到精通的完整指南
开发语言·后端·scala
旋风小飞棍3 天前
如何在sheel中运行spark
大数据·开发语言·scala
rylshe13144 天前
在scala中sparkSQL连接mysql并添加新数据
开发语言·mysql·scala
MZWeiei5 天前
Spark任务调度流程详解
大数据·分布式·spark·scala
бесплатно5 天前
Scala流程控制
开发语言·后端·scala
Bin Watson13 天前
解决 Builroot 系统编译 perl 编译报错问题
开发语言·scala·perl
什么芮.16 天前
大数据应用开发和项目实战(2)
大数据·pytorch·sql·spark·scala
不要天天开心17 天前
Spark-Streaming核心编程:有状态转化操作与DStream输出
scala
欧先生^_^18 天前
Scala语法基础
开发语言·后端·scala