WebSurfer's Home

Filter by APML

ASP.NET Core のオプション パターン

by WebSurfer 28. September 2026 15:45

ASP.NET Core のオプション パターンについて調べて、いろいろ分かったことがありましたので、以下に備忘録として書いておくことにします。

検証用に作成した Config ページ

ASP.NET Core でのオプションパターンとは何かを簡単に書きますと、appsettings.json などの構成設定を、厳密な型が定義された POCO (Plain Old CLR Object) クラスにバインドし、型安全かつシンプルに扱うためのデザインパターンということです。詳しくは Microsoft のドキュメント「ASP.NET Core のオプション パターン」を見てください。

この記事では、OptionsConfigurationServiceCollectionExtensions.Configure メソッドのオーバーロード Configure<TOptions>(IServiceCollection, IConfiguration) を利用して、appsettings.json のあるセクションから情報を読んで、その情報を POCO に格納し、DI コンテナに登録して ASP.NET Core アプリで利用するケースについて説明します。

(1) appsettings.json

以下の内容とします。この中の SoapService セクションの内容を POCO に取り込みます。

{
  ・・・中略・・・

  "SoapService": {
    "SoapScheme": "https",
    "SoapServiceHost": "SoapDevService",
    "CustomerAccountManager": {
      "Endpoint": "{SoapScheme}://{SoapServiceHost}/CustomerAccountManager.asmx",
      "Username": "",
      "Password": "",
      "TimeoutSeconds": 30,
      "MaxReceivedMessageSize": 10485760
    }
  }
}

(2) POCO クラスの定義

POCO のクラス定義は以下の通りとします。パブリックプロパティの名前は appsettings.json の各項目の名前と一致させる必要があります。SoapService フィールドは Microsoft のドキュメントのサンプルコードにならって追加しました。アプリのコード(この記事では Program.cs)で "SoapService" 文字列をハードコーディングしないようにするために追加するとのことです。

namespace RazorPages.Models
{
    public class SoapServiceOptions
    {
        public const string SoapService = "SoapService";
        public string SoapScheme { get; set; } = "";
        public string SoapServiceHost { get; set; } = "";
        public CustomerAccountManagerConfig CustomerAccountManager { get; set; } = new();
    }

    public class CustomerAccountManagerConfig
    {
        public string Endpoint { get; set; } = "";
        public string Username { get; set; } = "";
        public string Password { get; set; } = "";
        public int TimeoutSeconds { get; set; } = 0;
        public int MaxReceivedMessageSize { get; set; } = 0;
    }
}

(3) Configure メソッドを追加

Program.cs に以下のように Configure メソッドを追加します。Configure メソッドは内部で次のことを行います: (1) IConfiguration のセクションを読み取る、(2) Bind() を使って TOptions に値をコピーする、(3) 結果 Options パターン(IOptions<TOptions>, IOptionsSnapshot<TOptions>, IOptionsMonitor<TOptions>)が DI コンテナに登録されます。

namespace RazorPages
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // ・・・中略・・・

            // Configure メソッドを追加
            builder.Services.Configure<SoapServiceOptions>(
                builder.Configuration.GetSection(SoapServiceOptions.SoapService));

            // ・・・中略・・・
        }
    }
}

Options パターン IOptions<TOptions>, IOptionsSnapshot<TOptions>, IOptionsMonitor<TOptions> の違いを簡単に説明すると以下の通りです。

  1. IOptions<TOptions>(シングルトン相当)アプリ起動時に設定ファイルを読み込み、常に同じ設定値を保持し続けます。
  2. IOptionsSnapshot<TOptions>(スコープ相当)IOptionsSnapshot を使ってDIを受け取ると、HTTPリクエストごとに appsettings.json の変更を検知して最新の値に再生成してくれます。
  3. IOptionsMonitor<SoapService>(シングルトン相当+リアルタイム通知)シングルトンでありながら、設定ファイルが書き換わった瞬間に最新の値を反映・通知できる特殊なライフサイクルです。

(4) 一部内容を書き換え

PostConfigure を使って一部の内容を書き換えます。Program.cs で Configure メソッドの後に以下のようにコードを追加します。SoapServiceHost に DateTime.Now.ToString() を設定したのは書き変わるタイミングを知るためです。IOptionsSnapshot と IOptionsMonitor で SoapServiceHost の書き換えのタイミングがどのように異なるかが興味のあるところです。

namespace RazorPages
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // ・・・中略・・・

            // Configure メソッドを追加
            builder.Services.Configure<SoapServiceOptions>(
                builder.Configuration.GetSection(SoapServiceOptions.SoapService));

            // 一部内容の書き換え
            builder.Services.PostConfigure<SoapServiceOptions>(soap =>
            {
                soap.SoapServiceHost = DateTime.Now.ToString();
                soap.CustomerAccountManager.Endpoint = "/new-endpoint";
            });

            // ・・・中略・・・
        }
    }
}

(5) ページ/コントローラーでの情報の取得

ページ/コントローラーでの情報の取得は、ASP.NET Core に備わっている DI 機能を使って、コンストラクタの引数に IOptions<TOptions>, IOptionsSnapshot<TOptions>, IOptionsMonitor<TOptions> を注入してやることで可能です。以下のコードは Razor Pages の例で、この記事の一番上に貼った画像に示すページを表示したものです。

Config.html.cs

using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Options;
using RazorPages.Models;

namespace RazorPages.Pages
{
    public class ConfigModel : PageModel
    {
        private readonly SoapServiceOptions _soap;
        private readonly SoapServiceOptions _soapSnapshot;
        private readonly SoapServiceOptions _soapMonitor;

        public ConfigModel(IOptions<SoapServiceOptions> soap, 
               IOptionsSnapshot<SoapServiceOptions> soapSnapshot, 
               IOptionsMonitor<SoapServiceOptions> soapMonitor)
        {
            _soap = soap.Value;
            _soapSnapshot = soapSnapshot.Value;
            _soapMonitor = soapMonitor.CurrentValue;
        }

        public string SoapScheme => _soap.SoapScheme;
        public string SoapServiceHost => 
            _soap.SoapServiceHost;
        public string SoapEndpoint => 
            _soap.CustomerAccountManager.Endpoint;

        public string SoapSchemeSnapshot => 
            _soapSnapshot.SoapScheme;
        public string SoapServiceHostSnapshot => 
            _soapSnapshot.SoapServiceHost;
        public string SoapEndpointSnapshot => 
            _soapSnapshot.CustomerAccountManager.Endpoint;

        public string SoapSchemeMonitor => 
            _soapMonitor.SoapScheme;
        public string SoapServiceHostMonitor => 
            _soapMonitor.SoapServiceHost;
        public string SoapEndpointMonitor => 
            _soapMonitor.CustomerAccountManager.Endpoint;

        public void OnGet()
        {
        }
    }
}

Config.html

@page
@model RazorPages.Pages.ConfigModel

@{
    ViewData["Title"] = "Config";
}

<h3>IOptions<TOptions></h3>
<ul>
    <li>SoapScheme: @Model.SoapScheme</li>
    <li>SoapServiceHost: @Model.SoapServiceHost</li>
    <li>SoapEndpoint: @Model.SoapEndpoint</li>
</ul>

<h3>IOptionsSnapshot<TOptions></h3>
<ul>
    <li>SoapScheme: @Model.SoapSchemeSnapshot</li>
    <li>SoapServiceHost: @Model.SoapServiceHostSnapshot</li>
    <li>SoapEndpoint: @Model.SoapEndpointSnapshot</li>
</ul>

<h3>IOptionsMonitor<TOptions></h3>
<ul>
    <li>SoapScheme: @Model.SoapSchemeMonitor</li>
    <li>SoapServiceHost: @Model.SoapServiceHostMonitor</li>
    <li>SoapEndpoint: @Model.SoapEndpointMonitor</li>
</ul>

(6) 検証結果の説明

この記事の一番上の画像は検証用に作成した ASP.NET Core Razor Pages アプリの Config ページを表示したものです。赤および青の下線を引いた部分の違いがポイントです。

IOptions<TOptions> はシングルトンですので、アプリを再起動しない限り常に同じ値を保持し続けます。appsettings.json ファイルの SoapScheme を https ⇒ http に書き換えても、Program.cs の PostConfigure メソッドで時刻を設定し直しても、赤および青の下線を引いた部分は変わりません。

IOptionsSnapshot<TOptions> スコープ相当ですので、HTTP 要求ごとに appsettings.json の変更および PostConfigure メソッドでの書き換え検知して最新の値に再生成してくれます。上の画像は appsettings.json ファイルの SoapScheme を https ⇒ http に書き直して保存した後、再度 HTTP リクエストをかけてその応答を示したものです。この後も SoapServiceHost は要求・応答の度にその時刻に変わります。

IOptionsMonitor<TOptions> は基本シングルトンですが、appsettings.json ファイルが書き換わった瞬間に最新の値を反映してくれます。上の画像は appsettings.json ファイルの SoapScheme を https ⇒ http に書き直して保存した後、再度 HTTP リクエストをかけてその応答を示したものです。SoapServiceHost は appsettings.json ファイルが書き換わった時刻になります。その後はシングルトンとして動きますので、appsettings.json ファイルを書き換えない限り何度 HTTP リクエストをかけても値は変わりません。


上記のような Page や Controller で値を受け取るという使い方なら IOption または IOptionSnapshot を使えばよく、IOptionsMonitor の出番は無さそうです。IOptionsMonitor はこのような使い方ではなくて、IOptionsMonitor<TOptions>.OnChange メソッドでリスナーを登録し、設定変更を即時検知してそのリスナーでサーバー側で何か動かす(キャッシュ更新、長寿命サービスの設定差し替え、接続中クライアントへの通知など)のに使うのが適切なようです。

IOptionsMonitor<TOptions>.OnChange メソッドを使った例は別の記事に書く予定です。

Tags: , , , ,

CORE

コレクションのモデルバインディング

by WebSurfer 12. May 2026 18:33

ASP.NET Core MVC または Razor Pages アプリで、ユーザーがコレクションの要素の一部を削除して残りの要素を POST してきた場合に、送信されてきた残りの要素からなるコレクションのモデルバインディングを行うにはどうするかという話を書きます。

コレクションのモデルバインディング

先の記事「コレクションのデータアノテーション検証」に書きましたように、コレクションのモデルバインディングがうまく行われるようにするには、レンダリングされる input 要素の name 属性が連番のインデックスを含むようにします。具体的には、name="prefix[index].Property" というパターンとし、index は 0 から始まる連番とします。

この記事の例として上の画像のようなアプリを考えます。ユーザーが[Remove]ボタンをクリックしてある項目を削除してから[Submit]ボタンをクリックすると、残った項目のコレクションが POST されます。

その場合、POST されたコレクションの input 要素の name="prefix[index].Property" 属性の index が連続しなくなってしまいます。

index の数字の連続が途切れた場合、何も対応がされてなければそれ以降の要素はバインドされません。では、バインドされるようにするにはどういう対応を取ったらいいのでしょうか?

Microsoft のドキュメント「ASP.NET Core でのモデル バインド」の Collections のセクションの 5 つ目のコード例にある、selectedCourses.index=a&selectedCourses.index=b というような index を示すデータを一緒にPOST することにより連番でなくてもモデルバインドされるようになります。

例えば、index が 0 の場合、ASP.NET から以下の様な html 要素がレンダリングされるようにコーディングします。

<input type="hidden" name="prefix.Index" value="0" />
<input name="prefix[0].Property" value="ABC" />

参考に上の画像の ASP.NET Core Razor Pages アプリのコードを以下に載せてておきます。Visual Studio 2026 のテンプレートを使ってターゲットフレームワークを .NET 10 として作成したものです。

Model

using System.ComponentModel.DataAnnotations;

namespace RazorPages.Models
{    public class Country
    {
        [Required(ErrorMessage = "{0} は必須")]
        [StringLength(15, ErrorMessage = "{0} は {1} 文字以内")]
        [Display(Name = "国名")]
        public string Name { get; set; } = string.Empty;

        public CountryInfo Details { get; set; } = new();
    }

    public class CountryInfo
    {
        [Required(ErrorMessage = "{0} は必須")]
        [StringLength(15, ErrorMessage = "{0} は {1} 文字以内")]
        [Display(Name = "首都")]
        public string Capital { get; set; } = string.Empty;

        [Required(ErrorMessage = "{0} は必須")]
        [StringLength(15, ErrorMessage = "{0} は {1} 文字以内")]
        [Display(Name = "大陸")]
        public string Continent { get; set; } = string.Empty;
    }

    public class ListCountriesViewModel
    {
        public List<Country> CountryList { get; set; } = new();
        public List<Country> SelectedCountries { get; set; } = new();
    }
}

CountryList.cshtml.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPages.Models;

namespace RazorPages.Pages
{
    public class CountryListModel : PageModel
    {
        [BindProperty]
        public ListCountriesViewModel List { get; set; } = new();

        public void OnGet()
        {
            List = new ListCountriesViewModel
            {
                CountryList = GetDefaultCountries()
            };
        }

        public IActionResult OnPost()
        {
            if (ModelState.IsValid)
            {
                var posted = List.CountryList;
                List = new ListCountriesViewModel
                {
                    CountryList = posted,
                    SelectedCountries = posted
                };

                // POST された値は ModelState ディクショナリ(model ではない)
                // に格納されていて、Tag Helper はまず ModelState ディクショ
                // ナリを調べ、そこに値があればそれを表示する仕組みになっている。
                // なので、posted の値の通り表示するには ModelState をクリア
                // する必要がある
                ModelState.Clear();                
            }
            else
            {
                List = new ListCountriesViewModel
                {
                    CountryList = GetDefaultCountries()
                };
            }

            return Page();
        }

        private static List<Country> GetDefaultCountries()
        {
            var countries = new List<Country>
            {
                new Country()
                {
                    Name = "Italy",
                    Details = new CountryInfo
                    {
                        Capital = "Rome",
                        Continent = "Europe"
                    }
                },
                new Country()
                {
                    Name = "Spain",
                    Details = new CountryInfo
                    {
                        Capital = "Madrid",
                        Continent = "Europe"
                    }
                },
                new Country()
                {
                    Name = "USA",
                    Details = new CountryInfo
                    {
                        Capital = "Washington",
                        Continent = "NorthAmerica"
                    }
                },
                new Country()
                {
                    Name = "Japan",
                    Details = new CountryInfo
                    {
                        Capital = "Tokyo",
                        Continent = "Asia"
                    }
                },
                new Country()
                {
                    Name = "Australia",
                    Details = new CountryInfo
                    {
                        Capital = "Canberra",
                        Continent = "Oceania"
                    }
                }
            };

            return countries;
        }
    }
}

CountryList.cshtml

@page
@model RazorPages.Pages.CountryListModel

<h4>Remove countries not to submit</h4>
<form method="post">
    <table class="table">
        <thead>
            <tr>
                <th>
                    <label asp-for="List.CountryList[0].Name"></label>
                </th>
                <th>
                    <label asp-for="List.CountryList[0].Details.Capital"></label>
                </th>
                <th>
                    <label asp-for="List.CountryList[0].Details.Continent"></label>
                </th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            @for (int i = 0; i < Model.List.CountryList.Count; i++)
            {
                <tr class="country-row">
                    <input type="hidden" name="List.CountryList.Index" value="@i" />
                    <td>
                        <input asp-for="List.CountryList[i].Name" class="form-control" />
                        <span asp-validation-for="List.CountryList[i].Name" 
                            class="text-danger"></span>
                    </td>
                    <td>
                        <input asp-for="List.CountryList[i].Details.Capital" 
                            class="form-control" />
                        <span asp-validation-for="List.CountryList[i].Details.Capital" 
                            class="text-danger"></span>
                    </td>
                    <td>
                        <input asp-for="List.CountryList[i].Details.Continent" 
                            class="form-control" />
                        <span asp-validation-for="List.CountryList[i].Details.Continent" 
                            class="text-danger"></span>
                    </td>
                    <td>
                        <button type="button"
                                class="btn btn-danger"
                                onclick="this.closest('.country-row').remove();">
                            Remove
                        </button>
                    </td>
                </tr>
            }
        </tbody>
    </table>
    <input type="submit" value="Submit" class="btn btn-primary" />
</form>

<hr />
     
<h4>Countries submitted</h4>
<ul>
    @if (Model.List.SelectedCountries != null &&
         Model.List.SelectedCountries.Count > 0)
    {
        foreach (var country in Model.List.SelectedCountries)
        {
            <li>@country.Name</li>
        }
    }
    else
    {
        <li>No countries submitted.</li>
    }
</ul>

@section Scripts {
    @{
        await Html.RenderPartialAsync("_ValidationScriptsPartial");
    }
}

Tags: , , ,

CORE

.NET 10 ASP.NET Core Web API で Swagger を使用(その2)

by WebSurfer 4. April 2026 12:35

.NET 10 の ASP.NET Core Web API プロジェクトにデフォルトでインストールされている Microsoft.AspNetCore.OpenApi (以降「組込み OpenAPI」と書きます) と SwaggerUI (UI だけ) を使って [Authorize] ボタンを表示し、ベアラートークン (JWT) を使っての認証に必要な操作を行うことができるよう実装してみました。

SwaggerUI

先の記事「.NET 10 ASP.NET Core Web API で Swagger を使用」では SwaggerGen + SwaggerUI を使って実装しましたが、.NET 10 で組込み OpenAPI が使える状況ではそれは本筋ではなさそうですので。

過去はどうしていたのかも知っておいた方が理解しやすいと思いますので、.NET 8 からの経緯を以下に書きます。

  1. Visual Studio のテンプレートを使って作成する .NET 8 の ASP.NET Core Web API プロジェクトでは Swashbuckle.AspNetCore がデフォルトでインストールされている。
  2. Swashbuckle の SwaggerGen が OpenAPI ドキュメントを生成し、SwaggerUI が OpenAPI ドキュメントを解釈してブラウザ上で API のテストを行うことができる UI を提供していた。(参考: Swashbuckle と ASP.NET Core を始めよう)
  3. .NET 9 以降では ASP.NET Core Web API プロジェクトには組込み OpenAPI が含まれるようになった。それゆえ Swashbuckle は含まれなくなった。(参考: Announcement: Swashbuckle.AspNetCore is being removed in .NET 9 )
  4. SwaggerUI は、組込み OpenAPI が生成する OpenAPI ドキュメントを解釈して UI を提供できる。したがって、組込み OpenAPI + SwaggerUI (UI だけ) で .NET 8 の時と同様にブラウザ上で API のテストができる。(参考: ローカルのアドホック テストに Swagger UI を使用する)
  5. ただし、 [Authorize] ボタンを表示し、ベアラートークンを使っての認証に必要な操作を行うことができるようするためには OpenAPI ドキュメントのカスタマイズが必要 (Security Scheme と Security Requirement の追加が必要)。
  6. OpenAPI ドキュメントをカスタマイズするには、組込み OpenAPI + SwaggerUI の場合、トランスフォーマーを用いる。(参考: OpenAPI ドキュメントのトランスフォーマー)
  7. Program.cs でトランスフォーマーを実装し、OpenAPI ドキュメントに Security Scheme と Security Requirement を追加すれば、前者により [Authorize] ボタンの表示とトークンの設定、後者によりベアラートークンの送信ができるようになる。

ということで、下の画像の通り、組込み OpenAPI (Microsoft.AspNetCore.OpenApi 10.0.5) と Swashbuckle.AspNetCore.SwaggerUI 10.1.7 をインストールし、[Authorize] ボタンを表示して認証に必要な操作を行うことができるよう実装してみました。

NuGet パッケージ

トランスフォーマーを実装した Program.cs のコードは以下の通りです。「方法1」のコードは Securing OpenAPI and Swagger UI with OAuth in .NET 10 を参考に、「方法2」のコードはドキュメント トランスフォーマーを使用するを参考にしました。下のコード例では、「方法2」の登録をコメントアウトしてありますので、「方法1」のみが有効になっています。どちらの方法でも OpenAPI ドキュメントのカスタマイズ結果は同じになります。

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi;
using System.Text;

namespace WebApi3
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            builder.Services.AddControllers();

            // JWT 認証の設定
            builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = true,
                        ValidateAudience = true,
                        ValidateLifetime = true,
                        ValidateIssuerSigningKey = true,
                        ValidIssuer = builder.Configuration["Jwt:Issuer"],
                        ValidAudience = builder.Configuration["Jwt:Issuer"],
                        IssuerSigningKey = new SymmetricSecurityKey(
                            Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
                    };
                });

            builder.Services.AddAuthorization();

            // OpenAPI ドキュメントのトランスフォーマー設定(方法1)
            builder.Services.AddOpenApi(options =>
            {
                options.AddDocumentTransformer((document, context, cancellationToken) =>
                {
                    // OpenAPI ドキュメントの基本情報を設定(無くても可)
                    document.Info = new OpenApiInfo
                    {
                        Title = "ASP.NET Core Web API",
                        Version = "v1",
                        Description = "ASP.NET Core Web API with JWT authentication. " +
                            "Target Framework is .NET 10. " +
                            "Built‑in OpenAPI + SwaggerUI are used."
                    };

                    // Security Scheme を追加(JWT Bearer)
                    document.Components ??= new OpenApiComponents();
                    document.Components.SecuritySchemes ??=
                        new Dictionary<string, IOpenApiSecurityScheme>();
                    document.Components.SecuritySchemes.Add("Bearer",
                        new OpenApiSecurityScheme
                        {
                            Type = SecuritySchemeType.Http,
                            Scheme = "bearer",
                            BearerFormat = "JWT",
                            Description = "Please enter token"
                        });

                    // Security Requirement を追加
                    document.Security ??= new List<OpenApiSecurityRequirement>();
                    document.Security.Add(
                        new OpenApiSecurityRequirement
                        {
                            // インデクサ初期化子
                            [new OpenApiSecuritySchemeReference("Bearer", document)] = []
                        }
                    );

                    return Task.CompletedTask;
                });
            });

            // OpenAPI ドキュメントのトランスフォーマー設定(方法2)
            //builder.Services.AddOpenApi(options =>
            //{
            //    options.AddDocumentTransformer<JwtSecurityTransformer>();
            //});

            var app = builder.Build();

            if (app.Environment.IsDevelopment())
            {
                app.MapOpenApi();
                app.UseSwaggerUI(options =>
                {
                    options.SwaggerEndpoint("/openapi/v1.json", "v1");
                });
            }

            app.UseHttpsRedirection();

            // 認証と認可のミドルウェアを追加
            app.UseAuthentication();
            app.UseAuthorization();

            app.MapControllers();

            app.Run();

        }
    }

    // 方法2に用いる Transformer の実装
    // IAuthenticationSchemeProvider をコンストラクタへの DI で
    // 受け取ることで、登録されている認証スキームに Bearer が含
    // まれることを確認してから Security Scheme を追加する
    internal sealed class JwtSecurityTransformer(IAuthenticationSchemeProvider provider)
        : IOpenApiDocumentTransformer
    {
        public async Task TransformAsync(OpenApiDocument document,
                                         OpenApiDocumentTransformerContext context,
                                         CancellationToken cancellationToken)
        {
            // OpenAPI ドキュメントの基本情報を設定(無くても可)
            document.Info = new OpenApiInfo
            {
                Title = "ASP.NET Core Web API",
                Version = "v1",
                Description = "ASP.NET Core Web API with JWT authentication. " +
                            "Target Framework is .NET 10. " +
                            "Built‑in OpenAPI + SwaggerUI are used."
            };

            // Security Scheme を設定(JWT Bearer)
            var authenticationSchemes = await provider.GetAllSchemesAsync();
            if (authenticationSchemes.Any(authScheme => authScheme.Name == "Bearer"))
            {
                var securitySchemes = new Dictionary<string, IOpenApiSecurityScheme>
                {
                    // インデクサ初期化子
                    ["Bearer"] = new OpenApiSecurityScheme
                    {
                        Type = SecuritySchemeType.Http,
                        Scheme = "bearer",
                        BearerFormat = "JWT",
                        Description = "Please enter token"
                    }
                };
                document.Components ??= new OpenApiComponents();
                document.Components.SecuritySchemes = securitySchemes;
            }

            // Security Requirement を設定
            document.Security = [
                new OpenApiSecurityRequirement
                {
                    [new OpenApiSecuritySchemeReference("Bearer", document)] = []
                }
            ];
        }
    }
}

上のトランスフォーマーにより OpenAPI ドキュメントに Security Scheme と Security Requirement が追加されます。下の画像を見てください。赤枠が OpenAPI ドキュメント上の Security Scheme 部分、青枠が Security Requirement 部分になります。

OpenAPI ドキュメント

SwaggerUI は、上の赤枠部分を解釈して [Authorize] ボタンの表示とトークンの設定、青枠部分を解釈してベアラートークンの送信ができる実装を追加します。

Tags: , , , , ,

DevelopmentTools

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。ブログ2はそれ以外の日々の出来事などのトピックスになっています。

Calendar

<<  October 2026  >>
MoTuWeThFrSaSu
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar