WebSurfer's Home

Filter by APML

.NET 10 ASP.NET Core Web API で Swagger を使用

by WebSurfer 1. April 2026 12:04
2026/4/2 追記: この記事は、.NET 10 の組込み OpenAPI は使わないで、SwaggerGen + SwaggerUI を使った場合の話です。後で調べて分かったのですが、組込み OpenAPI と Swagger UI (UI だけ)を使って [Authorize] ボタンを表示し必要な操作を行うことは可能でした。その方法については「その2」に書きました。

ターゲットフレームワークを .NET 10 として作成した ASP.NET Core Web API プロジェクトで Swagger を使うにはどうすればよいかを調べましたので、備忘録として以下に書いておきます。

Swagger

ターゲットフレームワーク .NET 8 で作成した ASP.NET Core Web API プロジェクトにはデフォルトで Swagger か組み込まれており、Visual Studio からアプリを実行すると上の画像のようにブラウザ上に Swagger UI が表示され、それを操作して Web API にデータを送信し応答を受け取ることができました。

しかしながら、ターゲットフレームワーク .NET 9 以降の ASP.NET Core Web API のプロジェクトには Swagger は含まれません (理由は Announcement: Swashbuckle.AspNetCore is being removed in .NET 9 を見てください)。それを .NET 8 プロジェクトと同様に Swagger を利用できるようにするにはどうすれば良いかという話です。

プロジェクトの作成に Visual Studio 2026 のテンプレート ASP.NET Core Web API を使ってターゲットフレームワーク .NET 10 とすると、設定画面で [Enable OpenAPI support] にデフォルトでチェックが入っており、作成したプロジェクトには NuGet パッケージ Microsoft.AspNetCore.OpenApi がインストールされ、Program.cs にはそれを使う設定がされます。

しかし、Microsoft.AspNetCore.OpenApi には、Web API を操作するための組み込みのサポートは付属していませんので、.NET 8 と同様に Swagger を使いたいのであれば追加で Swagger UI をインストールする必要があります。

具体的には、Microsoft のドキュメント「ローカルのアドホック テストに Swagger UI を使用する」に書いてありますように、NuGet パッケージ Swashbuckle.AspNetCore.SwaggerUI をインストールし、swagger-ui ミドルウェアを有効にします。それで先の記事「ASP.NET Core Web API と Swagger(その1)」に書いたことまでは Swagger UI + Microsoft.AspNetCore.OpenApi でできるようになります。

(ただし、SampleB メソッドには [Consumes("multipart/form-data")] 属性を付与しないと、Swagger はコンテンツは正しく multipart/form-data 形式とするものの、応答ヘッダーは Content-Type: application/x-www-form-urlencoded としてしまうという問題がありました。Swagger UI + Microsoft.AspNetCore.OpenApi 併用で、引数に複合モデルを使った場合に起きるバグ的な挙動のようです)

問題は、Swagger UI + Microsoft.AspNetCore.OpenApi 併用では、先の記事「ASP.NET Core Web API と Swagger(その2)」に書いた AddSwaggerGen メソッドが使えず、[Authorize] ボタンを表示できないことです。当然、[Authorize] ボタンをクリックしてベアラトークン入力ウィンドウを表示し、テキストボックスにトークンを入力して、トークンを要求ヘッダに含めて送信するという操作はできません。

Copilot によると、[Authorize] ボタンを出したいなら SwaggerGen + SwaggerUI を使う他に方法はないとのこと。実はそれは誤りだったのですが、とりあえずその方向に進んで、以下のように NuGet パッケージ Microsoft.AspNetCore.OpenApi はアンインストールし、Swashbuckle.AspNetCore 10.1.7 をインストールしてこの記事の一番上の画像のように [Authorize] ボタンが出るよう実装してみました。

Swashbuckle.AspNetCore 10.1.7 をインストール

この時注意しなければならないのが、Swashbuckle 10.x では、GitHub の記事 Migrating to Swashbuckle.AspNetCore v10 に "Update any using directives that reference types from the Microsoft.OpenApi.Models namespace to use the new namespace Microsoft.OpenApi." と書いてあるように Microsoft.OpenApi.Models という名前空間がなくなって、そこにあったクラス類は Microsoft.OpenApi 名前空間に移動したということです。そして、全部移動した訳ではないので、using Microsoft.OpenApi; に変えるだけで Swashbuckle 9.x 以前の時代に使っていたコードがそのまま使えるわけではないということです。

今回、Swashbuckle 9.x 以前で使っていたコードをそのまま移植してどこが問題になったかというと、下の画像の通りです。

Security Requirement の追加

Swashbuckle 10.x には Reference も OpenApiReference も存在しません。理由は、Copilot に聞いた話ですが、以下の通りだそうです。

  • Swashbuckle 9.x までは OpenApiSecurityScheme, OpenApiReference, OpenApiSecurityRequirement など OpenAPI.NET のモデルを直接操作して Security を追加していた。
  • Swashbuckle 10.x では OpenAPI.NET のモデルを直接触る API が削除され、代わりに OpenAPI ドキュメントを後から加工する Transformer 方式に変更された。

(ちなみに、上の画像のコードの options.AddSecurityRequirement( ... を削除しても [Autorize] ボタンは表示されるものの、要求ヘッダに Authorization: Bearer ... が含まれなくなります)

Swashbuckle 10.x を使うなら options.AddSecurityRequirement( ... のコードを上で言う Transformer 方式で書き換えるということになります。Transformer というのは、これも Copilot に聞いた話ですが、SwaggerGen が生成した OpenAPI ドキュメントに対して追加・変更・削除を行う「後処理フィルター」のことだそうです。

Transformer 方式で書き直した options.AddSecurityRequirement( ... のコードは以下のようになります。下のコードの document は SwaggerGen が生成した OpenAPI ドキュメントで、そのドキュメントに対して Security Requirement を追加しています。

options.AddSecurityRequirement(document =>
    new OpenApiSecurityRequirement
    {
        [new OpenApiSecuritySchemeReference("Bearer", document)] = []
    });

これにより [Authorize] 機能は期待通り動くようになります。

この記事の一番上の画像の [Authorize] ボタンをクリックすると、下の画像のトークン入力ウィンドウが表示されるので、テキストボックスにトークンを入力し、下の画像の [Authorize] ボタンをクリックします。

トークン入力ウィンドウ

それ以降は Swagger からの要求は、すべて要求ヘッダに Authorization: Bearer <JWT> が含まれて送信されるようになります。

Fiddler で見た要求ヘッダ

以下にこの記事を書く際に検証用に使ったコードを載せておきます。

Program.cs

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

namespace WebApi2
{
    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();

            // Swagger の設定(Swashbuckle.AspNetCore 10.x を使う例)
            builder.Services.AddSwaggerGen(options =>
            {
                // OpenAPI ドキュメントの基本��報を設定(無くても可)
                options.SwaggerDoc("v1", new OpenApiInfo
                {
                    Title = "ASP.NET Core Web API",
                    Version = "v1",
                    Description = "ASP.NET Core Web API with JWT authentication. " +
                    "Target Framework is .NET 10. " +
                    "Swashbuckle.AspNetCore 10.1.7 is used."
                });

                // Security Scheme を追加(JWT Bearer トークンを使う)
                options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
                {
                    Name = "Authorization",
                    Type = SecuritySchemeType.Http,
                    Scheme = "Bearer",
                    BearerFormat = "JWT",
                    In = ParameterLocation.Header,
                    Description = "Please enter token"
                });

                // Security Requirement を追加(これが無いと Authorize ウィンドウ
                // で JWT を設定しても、Swagger は Authorization: Bearer ... を
                // 要求ヘッダに含めない)                
                // 下のコードの document は SwaggerGen が生成した OpenAPI ドキュ
                // メント。そのドキュメントに対して Security Requirement を追加
                options.AddSecurityRequirement(document =>
                    new OpenApiSecurityRequirement
                    {
                        [new OpenApiSecuritySchemeReference("Bearer", document)] = []
                    });
            });

            var app = builder.Build();

            if (app.Environment.IsDevelopment())
            {
                // Swagger ミドルウェアを有効にする
                app.UseSwagger();

                // Swagger UI を有効にする
                app.UseSwaggerUI(options =>
                {
                    options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
                });
            }

            app.UseHttpsRedirection();

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

            app.MapControllers();

            app.Run();
        }
    }
}

appsettings.json

上の Program.cs の JWT 認証の設定で使う Key と Issuer の値を設定しています。

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Jwt": {
    "Key": "veryVerySecretKeyWhichMustBeLongerThan32",
    "Issuer": "https://localhost:7032/"
  }
}

TokenController

Id と Password を受けて JWT を発行する API です。この記事の本題とは直接関係ないですが参考までに載せておきます。

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Text;

namespace WebApi2.Controllers
{
    [Route("[controller]")]
    [ApiController]
    public class TokenController : ControllerBase
    {
        private readonly IConfiguration _config;

        public TokenController(IConfiguration config)
        {
            _config = config;
        }

        [AllowAnonymous]
        [HttpPost]
        public async Task<IActionResult> CreateToken(LoginModel login)
        {
            string? id = login.Username;
            string? pw = login.Password;
            IActionResult response = Unauthorized();

            if (!string.IsNullOrEmpty(id) && !string.IsNullOrEmpty(pw))
            {
                // 受け取った id と password を検証する
                if (await VerifyUserAsync(id, pw))
                {
                    // JWT を生成する
                    var tokenString = BuildToken(_config);
                    response = Ok(new { token = tokenString });
                }
            }

            return response;
        }

        // 受け取った id と password を検証するヘルパメソッド
        // 内部で UserManager.CheckPasswordAsync(id, pw) を使うことを
        // 想定して非同期メソッドにした
        private static Task<bool> VerifyUserAsync(string id, string pw)
        {
            // ここでは全て検証結果 OK として true を返す
            return Task.FromResult(true);
        }

        // JWT を生成するヘルパメソッド
        private static string BuildToken(IConfiguration config)
        {
            var key = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(config["Jwt:Key"]!));

            var creds = new SigningCredentials(
                key, SecurityAlgorithms.HmacSha256);

            var token = new JwtSecurityToken(
                issuer: config["Jwt:Issuer"],
                audience: config["Jwt:Issuer"],
                claims: null,
                notBefore: null,
                expires: DateTime.Now.AddMinutes(30),
                signingCredentials: creds);

            return new JwtSecurityTokenHandler().WriteToken(token);
        }
    }


    // クライアントから送信されてきた id と password を受け取る
    // ための View Model
    public class LoginModel
    {
        public string? Username { get; set; }
        public string? Password { get; set; }
    }
}

UploadController

アップロードされたファイルを受け取る API です。これもこの記事の本題とは直接関係ないですが参考までに載せておきます。

using Microsoft.AspNetCore.Mvc;

namespace WebApi2.Controllers
{
    [Route("[controller]")]
    [ApiController]
    public class UploadController : ControllerBase
    {
        [HttpPost("SampleA")]
        public IActionResult SampleA(IFormFile? postedFile,
                                     [FromForm] string? customField)
        {
            if (postedFile == null || postedFile.Length == 0)
            {
                return Content("ファイルを受信できませんでした");
            }

            if (customField == null)
            {
                return Content("customField を受信できませんでした");
            }

            return Content($"ファイル: {postedFile.FileName}, " +
                $"customField: {customField} 受信");
        }

        [HttpPost("SampleB")]
        // 以下の属性は無くても問題無し
        //[Consumes("multipart/form-data")]
        public IActionResult SampleB([FromForm] UploadModels model)
        {
            if (model.PostedFile == null || model.PostedFile.Length == 0)
            {
                return Content("ファイルを受信できませんでした");
            }

            if (model.CustomField == null)
            {
                return Content("customField を受信できませんでした");
            }

            return Content($"ファイル: {model.PostedFile.FileName}, " +
                $"customField: {model.CustomField} 受信");
        }
    }

    public class UploadModels
    {
        public string? CustomField { get; set; }
        public IFormFile? PostedFile { get; set; }
    }
}

Tags: , , , ,

DevelopmentTools

ASP.NET Core Web API と Swagger(その2)

by WebSurfer 23. October 2024 18:06

Visual Studio 2022 のテンプレートを使ってターゲットフレームワーク .NET 8.0 で ASP.NET Core Web API のプロジェクトを作成し、Visual Studio から実行すると下の画像のようにブラウザ上に swagger/index.html というページが表示され、そこから Web API のアクションメソッドを要求して応答を調べることができます。(追記: 2024/11/25 時点で、ターゲットフレームワーク .NET 9.0 で作成した Web API プロジェクトには Swagger は含まれません。理由は、Announcement: Swashbuckle.AspNetCore is being removed in .NET 9 を見てください)

Swagger

Swagger を使って、(1) ファイルをアップロードする方法、及び (2) ベアラトークンを要求ヘッダに含めて送信する方法を調べましたので、備忘録として残しておくことにしました。

先の記事「ASP.NET Core Web API と Swagger(その1)」では (1) について書きました。この記事では (2) について書きます。

(2) ベアラトークンを要求ヘッダに含める

これについてはネットで検索して見つけた記事 OAuth Bearer Token with Swagger UI — .NET 6.0 が参考になりました。

テンプレートを使って作った ASP.NET Core Web API のプロジェクトの Program.cs に含まれている AddSwaggerGen メソッドを以下のように拡張します。コードに Use baerer token authorization header とコメントしてありますように、Type に SecuritySchemeType.Http を設定するのがポイントです。

//builder.Services.AddSwaggerGen();

builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo 
    { 
        Title = "My Web API", 
        Version = "v1" 
    });

    options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Name = "Authorization",

        // Use baerer token authorization header
        Type = SecuritySchemeType.Http,

        Scheme = "Bearer",
        BearerFormat = "JWT",
        In = ParameterLocation.Header,
        Description = "Please enter token",
    });

    options.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            new List<string>()
        }
    });
});

上の OpenApiSecurityRequirement クラスの初期化のコードが見慣れない形になっていますが、それについて説明しておきます。

OpenApiSecurityRequirement クラスは Dictionary<OpenApiSecurityScheme,IList<String>> を継承してます。なので、コレクション初期化子を使用してディクショナリを初期化するのと同様に new Dictionary<TKey, TValue> {{ key1, value 1}, { key2, value2 }} という形でキーと値のペアを Add しながら OpenApiSecurityRequirement クラスを初期化しています。

上のコードを実装すると、この記事の一番上の画像のように、ブラウザに表示される Swagger 画面の右上に赤枠で囲ったように[Authorize]ボタンが表示されるようになります。

それをクリックすると、下の画像のように Available authorizations ダイアログがポップアップ表示されるので、そのテキストボックスにトークンを入力して、ダイアログ上の[Authorize]ボタンをクリックすれば、以降は Swagger からの要求すべてにベアラトークンが要求ヘッダに含まれて送信されるようになります。

Available authorizations ダイアログ

下は Swagger からの要求を Fiddler でキャプチャした画像です。赤枠で示したように要求ヘッダにベアラトークンが含まれて送信されています。

要求ヘッダのベアラトークン

上の操作で設定したベアラトークンを送信しないようにするには、Swagger 画面右上の[Authorize]ボタンをクリックして Available authorizations ダイアログを表示し、[Logout]ボタンをクリックします。

ログアウト

Tags: , , , ,

DevelopmentTools

ASP.NET Core Web API と Swagger(その1)

by WebSurfer 22. October 2024 18:00

Visual Studio 2022 のテンプレートを使ってターゲットフレームワーク .NET 8.0 で ASP.NET Core Web API のプロジェクトを作成し、Visual Studio から実行すると下の画像のようにブラウザ上に swagger/index.html というページが表示され、そこから Web API のアクションメソッドを要求して応答を調べることができます。(追記: 2024/11/25 時点で、ターゲットフレームワーク .NET 9.0 で作成した Web API プロジェクトには Swagger は含まれません。理由は、Announcement: Swashbuckle.AspNetCore is being removed in .NET 9 を見てください)

Swagger

Swagger を使って (1) ファイルをアップロードする方法、及び (2) ベアラトークンを要求ヘッダに含めて送信する方法を調べましたので、備忘録として残しておくことにしました。

(1) と (2) を一つの記事に書くと長くなりすぎるので、この記事では (1) を書いて、(2) は別の記事に「その2」として書くことにします。

(1) ファイルアップロード

アクションメソッドの引数に IFormFile 型の変数を含めれば、Swagger が自動的にそれを検出してアップロードするファイルの選択が可能になり、Swagger から Web API にファイルをアップロードできるようになります。

Swagger にファイル選択のための画面を表示するには、まず、[Try it out]ボタンをクリックします。

[Try it out]ボタンをクリック

[Try it out]ボタンをクリックすると、下のように、ブラウザが html の <input type="file" > を表示した時と同様なファイル選択を行うための画面が表示されます。

アップロードするファイルの選択

その画面でアップロードするファイルを選択したら、[Execute]ボタンをクリックすれば選択したファイルは multipart/form-data 形式でサーバーに送信され、アクションメソッドの引数に渡されます。

下の Fiddler による要求のキャプチャ画像を見てください。

Fiddler による要求のキャプチャ画像

アクションメソッドへの引数に IFormFile 型の変数を含める方法ですが、下のコード例の SampleA メソッドのように直接含めても、SampleB メソッドのようにモデル経由で含めても、Swagger が自動的にそれを検出してくれます。上の画像は SampleA のものですが、SampleB でも同様になります。

using Microsoft.AspNetCore.Mvc;

namespace WebApi2.Controllers
{
    [Route("[controller]")]
    [ApiController]
    public class UploadController : ControllerBase
    {
        [HttpPost("SampleA")]
        public IActionResult SampleA(IFormFile? postedFile, 
                                     [FromForm] string? customField)
        {
            if (postedFile == null || postedFile.Length == 0)
            {
                return Content("ファイルを受信できませんでした");
            }

            if (customField == null)
            {
                return Content("customField を受信できませんでした");
            }

            return Content($"ファイル: {postedFile.FileName}, " +
                $"customField: {customField} 受信");
        }

        [HttpPost("SampleB")]
        public IActionResult SampleB([FromForm] UploadModels model)
        {
            if (model.PostedFile == null || model.PostedFile.Length == 0)
            {
                return Content("ファイルを受信できませんでした");
            }

            if (model.CustomField == null)
            {
                return Content("customField を受信できませんでした");
            }

            return Content($"ファイル: {model.PostedFile.FileName}, " +
                $"customField: {model.CustomField} 受信");
        }
    }

    public class UploadModels
    {
        public string? CustomField { get; set; }
        public IFormFile? PostedFile { get; set; }
    }
}

この記事の本題とは関係ない話ですが、上のサンプルコードで、SampleA の引数の型、SampleB が使う UploadModels のプロパティの型が null 許容型となっているのには理由がありますのでそれも書いておきます。

ユーザーがファイルを選択しないまま / customFiled が空白のまま[Execute]ボタンをクリックすると、null をアクションメソッドの引数にバインドしようとします。なので、引数を null 許容型にしておかないと、バインド時にエラーとなって HTTP 400 Bad Request が応答として返され、アクションメソッドは実行されません。

上のコード例で言うと、if 文以下は実行されないので期待した応答("customField を受信できませんでした" とか "ファイルを受信できませんでした")は返ってこないということになります。

Tags: , , , ,

DevelopmentTools

About this blog

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

Calendar

<<  July 2026  >>
MoTuWeThFrSaSu
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

View posts in large calendar