ASP.Net+Swagger+TypeScriptでEnumの表示名を自動生成する

garicchi

garicchi

Posted on January 25, 2024

ASP.Net+Swagger+TypeScriptでEnumの表示名を自動生成する

ASP.NetはSwaggerをサポートしており、Controllerを書くだけでopenapi schemaが自動生成されて便利です。

そして自動生成されたopenapi schemaから、openapi-generatorなどのツールを利用して、TypeScriptのコードを自動生成すれば、Single Page Applicationが作りやすくなります。

レスポンスのPayloadにEnum型があった場合

しかし、レスポンスのPayloadにEnum型のプロパティがあった場合、あまり良いコードが得られません。

例えば、こんなC#のenumがあったとして、

public enum WeatherType
{
    Sunny = 0,
    Cloudy = 1,
    Rainy = 2,
}
Enter fullscreen mode Exit fullscreen mode

普通にopenapi-generatorで自動生成すると、以下のTypeScriptのコードが得られます。

export const WeatherType = {
    NUMBER_0: 0,
    NUMBER_1: 1,
    NUMBER_2: 2
} as const;
export type WeatherType = typeof WeatherType[keyof typeof WeatherType];
Enter fullscreen mode Exit fullscreen mode

EnumのメンバーがNUMBER_{数字} になってわかりにくいです。

x-enum-varnames

メンバーのキー名を変更するには、openapi schemaにx-enum-varnamesという名前で配列を入れてやればよいです。
https://github.com/OpenAPITools/openapi-generator/issues/893#issuecomment-416617460

ASP.NetのSwaggerジェネレータ (今回はSwashbuckle)の場合、SchemaFilterという機能があり、これを使えば、出力されるopenapi schemaをいじることができます。

こんな感じのSchemaFilterを用意して

public class EnumSchemaFilter : ISchemaFilter
{
    public void Apply(OpenApiSchema model, SchemaFilterContext context)
    {
        if (context.Type.IsEnum)
        {
            var varNamesArray = new OpenApiArray();
            foreach (var memberVal in Enum.GetValues(context.Type))
            {
                var memberName = Enum.GetName(context.Type, memberVal);
                varNamesArray.Add(new OpenApiString(memberName));
            }

            model.Extensions.Add("x-enum-varnames", varNamesArray);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

AddSwaggerGenで指定してやります

builder.Services.AddSwaggerGen(options =>
{
    options.SchemaFilter<EnumSchemaFilter>();
});
Enter fullscreen mode Exit fullscreen mode

すると、openapi schemaにはx-enum-varnamesにメンバー名が入るようになります。

"WeatherType": {
  "enum": [
    0,
    1,
    2
  ],
  "type": "integer",
  "format": "int32",
  "x-enum-varnames": [
    "Sunny",
    "Cloudy",
    "Rainy"
  ]
}
Enter fullscreen mode Exit fullscreen mode

これを使ってTypeScriptを生成すると、キーが正しくメンバー名になります。

export const WeatherType = {
    Sunny: 0,
    Cloudy: 1,
    Rainy: 2
} as const;
export type WeatherType = typeof WeatherType[keyof typeof WeatherType];
Enter fullscreen mode Exit fullscreen mode

表示名を変えたい

x-enum-varnamesを使えば、TypeScriptのオブジェクトのキーとして、Enumのメンバー名を使用できましたが、
実際にUIに値を表示したい時は、もっと違う文字列を表示したいかもしれません。

例えばメンバー名を Sunny= とした場合、= はTypeScriptのオブジェクトのキーとして使えないのでエラーになります。

また、各国の言語に翻訳した名前を表示したいかもしれません。

EnumのAttributeを表示する

そこで、EnumにAttributeをつけて、それを表示するようにしてみます。
今回は System.ComponentModel.DataAnnotations.DisplayAttribute を使用します。

public enum WeatherType
{
    [Display(Name = "晴れ")]
    Sunny = 0,

    [Display(Name = "曇り")]
    Cloudy = 1,

    [Display(Name = "雨")]
    Rainy = 2,
}
Enter fullscreen mode Exit fullscreen mode

openapi schemaのテキトーなキーに、この表示名をつけれればいいので、
EnumSchmeFilterを以下のように修正し、 x-enum-displays というスキーマを追加してみます。

public class EnumSchemaFilter : ISchemaFilter
{
    public void Apply(OpenApiSchema model, SchemaFilterContext context)
    {
        if (context.Type.IsEnum)
        {
            var varNamesArray = new OpenApiArray();
+           var displayArray = new OpenApiArray();
            foreach (var memberVal in Enum.GetValues(context.Type))
            {
                var memberName = Enum.GetName(context.Type, memberVal);
                varNamesArray.Add(new OpenApiString(memberName));
+               var obj = new OpenApiObject();
+               var display = (DisplayAttribute?)memberVal.GetType().GetMember(memberVal.ToString() ?? "").FirstOrDefault()?.GetCustomAttributes(typeof(DisplayAttribute), true).FirstOrDefault();
+               obj["value"] = new OpenApiInteger(Convert.ToInt32(memberVal));
+               obj["display"] = new OpenApiString(display?.Name);
+               displayArray.Add(obj);
            }

            model.Extensions.Add("x-enum-varnames", varNamesArray);
+           model.Extensions.Add("x-enum-displays", displayArray);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

これで生成されたスキーマは以下のようになります。

"WeatherType": {
  "enum": [
    0,
    1,
    2
  ],
  "type": "integer",
  "format": "int32",
  "x-enum-varnames": [
    "Sunny",
    "Cloudy",
    "Rainy"
  ],
  "x-enum-displays": [
    {
      "value": 0,
      "display": "晴れ"
    },
    {
      "value": 1,
      "display": "曇り"
    },
    {
      "value": 2,
      "display": "雨"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

あとはこれを使って、TypeScriptコードを生成してやればよいのですが、標準では今回作った x-enum-displays を読めないので、コード生成テンプレートを修正してやります。

テンプレートはここにあるので落としてきます
https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator/src/main/resources/typescript-fetch

次に、テンプレートの中にある modelEnumInterfaces.mustache に以下を追加します。

/*
customized
*/
{{^stringEnums}}
{{#vendorExtensions}}
export function Get{{classname}}DisplayName(v: {{classname}}) {
    switch(v) {
{{#x-enum-displays}}
        case {{value}}:
            return "{{display}}";
{{/x-enum-displays}}
        default:
            throw new Error(`value ${v} is not supported`);
    }
}
{{/vendorExtensions}}
{{/stringEnums}}
Enter fullscreen mode Exit fullscreen mode

あとはopenapi generatorで生成する時に、 -t オプションで、先ほど修正したテンプレートのディレクトリを指定します。

テンプレートのカスタマイズはこのドキュメントを参考にしてください。

これで、生成されたTypeScriptには、enumから表示名を取得する関数が追加されます。

export const WeatherType = {
    Sunny: 0,
    Cloudy: 1,
    Rainy: 2
} as const;
export type WeatherType = typeof WeatherType[keyof typeof WeatherType];

/*
customized
*/
export function GetWeatherTypeDisplayName(v: WeatherType) {
    switch(v) {
        case 0:
            return "晴れ";
        case 1:
            return "曇り";
        case 2:
            return "";
        default:
            throw new Error(`value ${v} is not supported`);
    }
}
Enter fullscreen mode Exit fullscreen mode

こんな感じで表示名を取得できます。

// 晴れ が返される
GetWeatherTypeDisplayName(WeatherType.Sunny)
Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
garicchi
garicchi

Posted on January 25, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related