【ASP.NET Core Web API】列挙型(Enum)を定数値だけでなく名前でも扱えるようにする
ASP.NET Core Web API のエンドポイント定義に、列挙型 (Enum) のプロパティを持つクラスを使うことがあります。その場合、Enum の値はデフォルトでは定数値(数値)として扱われますが、定数値に加えてメンバー名でも扱えると便利なことが多いと思います。簡単な設定を追加するだけですが、Minimal API を使う場合にちょっとした落とし穴があるので、備忘録としてその方法を載せます。
C# での ASP.NET Core Web API の実装を前提とします。.NET 7 および 8 で確認済みです(.NET 6 にも適用できると思いますが、未確認です)。
設定方法だけ知りたい方はこちらに跳んでください。
デフォルトの動き
まずデフォルトの動きを確認します。ASP.NET Core Web API のエンドポイントでクラスの型を使うのは主に以下の3箇所です。
- HTTP リクエストボディ
- HTTP レスポンスボディ
- HTTP GET メソッドのクエリ文字列(URL パラメータ)
これらの定義に以下のような Enum とクラスを使うケースを考えます。
public enum League
{
American = 1,
National = 2,
}
public record class Player
{
public required string Name { get; init; } = default!;
public required League League { get; init; }
}
HTTP リクエストボディ
リクエストボディの型となるクラスは、HTTP では JSON として扱われます。JSON となるクラス内に Enum プロパティがある場合は、JSON 上での値は数値(Enum の定数値)となります。例えば上記の Player クラスをリクエストボディにした場合、JSON は次のようになります。
{
"name": "Shohei Ohtani",
"league": 2
}
この JSON の league プロパティ値を Enum のメンバー名にしてエンドポイントに渡すと、デフォルトでは HTTP 400 エラーが返ってきてしまいます。
{
"name": "Shohei Ohtani",
"league": "National"
}
記事後半にある設定を追加することで、このような JSON でもリクエストが通るように変更できます。
HTTP レスポンスボディ
レスポンスボディとして Player クラスを使う場合、デフォルトでは次の JSON が返されます。
{
"name": "Shohei Ohtani",
"league": 2
}
設定を追加すると、次のように Enum プロパティの値がメンバー名の文字列となった JSON を返すようになります。
{
"name": "Shohei Ohtani",
"league": "National"
}
HTTP GET メソッドのクエリ文字列(URL パラメータ)
ASP.NET Core Web API では、HTTP GET エンドポイントのクエリ文字列の型にもクラスを使えます。その場合、クラスの各プロパティがクエリ文字列の一つ一つの変数に置き換えられます。クエリ文字列の場合、Enum のプロパティに該当する変数として定数値・メンバー名のどちらを渡しても動作します。例えば、以下のどちらのリクエストも、デフォルトで正しく応答が返ります。
https://localhost/Players?League=2
https://localhost/Players?League=National
問題は Swagger です。クラスをクエリ文字列の型として使った場合、Swagger は HTTP GET エンドポイントの Enum プロパティをプルダウンとして表示してくれるのですが、そのアイテムリストがデフォルトでは定数値になります。
Swagger では、以下のようにメンバー名を列挙してくれる方が使いやすいかもしれません。これも次項の設定を追加することで変更できます。
設定の方法
ASP.NET Core Web API プロジェクトは、コントローラー版と Minimal API 版、2つの実装方式を選べます。どちらも、プロジェクトの Program.cs で JsonStringEnumConverter を追加することで、プロジェクト内のすべてのエンドポイントにおいて Enum を文字列として扱えるようになります。それに加えて、Enum 自体に JsonStringEnumConverter を設定する方法もあります。
Web API プロジェクト全体に適用する: コントローラー
コントローラーの場合、Program.cs で AddControllers の後ろに AddJsonOptions メソッドを繋げます。
builder.Services
.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
});
あるいは、AddControllers とは別の行に以下を追加する方法もあります。
builder.Services.Configure<Microsoft.AspNetCore.Mvc.JsonOptions>(options =>
{
options.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
});
Web API プロジェクト全体に適用する: Minimal API
Minimal API の場合、Program.cs に以下を追加します。
// For Minimal API
builder.Services.Configure<Microsoft.AspNetCore.Http.Json.JsonOptions>(options =>
{
options.SerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
});
// For Swashbuckle.AspNetCore
builder.Services.Configure<Microsoft.AspNetCore.Mvc.JsonOptions>(options =>
{
options.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
});
Minimal API の場合はこのように、二つの JsonOptions に JsonStringEnumConverter を追加しています。一つ目の Microsoft.AspNetCore.Http.Json.JsonOptions は Minimal API からの参照用です。
二つ目は前項のコントローラーの場合と同じ設定です。なぜこれも追加するかというと、Swagger のためです。Swashbuckle.AspNetCore のライブラリが(執筆時点のバージョンは 6.5.0)、Minimal API とは別のこちらの JsonOptions を参照するようで、別途追加が必要になります。それで、もし Swagger を使うのでなければこちらは不要です。
なお Microsoft.AspNetCore.Http.Json.JsonOptions の方は、以下のような書き方もできます。
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
});
Enum に属性をつける
ASP.NET Core の Program.cs でではなく、C# の Enum 自体に JsonStringEnumConverter を設定するやり方もあります。Web API プロジェクト全体に適用する必要がないケースや、Web API 以外の箇所でも JSON シリアライズ・デシリアライズをする時などは、こちらの方法を使えます。
以下のように Enum に JsonConverter 属性をつけます。
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum League
{
American = 1,
National = 2,
}
[JsonConverter(typeof(JsonStringEnumConverter<League>))]
public enum League
{
American = 1,
National = 2,
}
さらに、クラスのプロパティに JsonConverter 属性をつける、というやり方もあります。ただこのやり方だと Swagger の Example で Enum がメンバー名表示にならないようですので、通常は Enum そのものに属性を付加する方が良いかもしれません。
public record class Player
{
public required string Name { get; init; } = default!;
[JsonConverter(typeof(JsonStringEnumConverter))]
public required League League { get; init; }
}
Discussion