Azure Functions .NET 8 分離ワーカーモデルで Swagger のトップページをカスタマイズする

# やりたいこと
- Swagger UI のトップページに表示される見出しやメッセージ、バージョン情報などをカスタマイズする

# 環境
- Visual Studio Community 2022
    - Version 17.11.1
- .NET 8
- Azure Functions 分離ワーカーモデル

# やったこと
## まず現状の確認

以前 .NET 8 分離ワーカーモデルの Azure Functions で OpenAPI/Swagger を構成できました。

https://qiita.com/higege-amdeker/items/37bef18996a2b95c62ff

しかしよく見ると Swagger UI のトップページがデフォルトのまま。

こんなかんじ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/9ed987ff-543f-80ca-0681-7f1b2f2745c1.png)

何かしらカスタマイズしたいですね。

## JSON ファイルを確認する
Swagger UI の上部アドレスバーに表示されている JSON ファイルに直接アクセスしてみます。

```json
{
    "swagger": "2.0",
    "info": {
        "title": "OpenAPI Document on Azure Functions",
        "description": "This is the OpenAPI Document on Azure Functions",
        "version": "1.0.0"
    },
    "host": "localhost:7299",
```
おっと、さっそく怪しい箇所を発見しました。どうやら自動生成されるこの `info` の部分を変えてやれば表示も変わりそうです。

# C# でどのように実装するか調査

関連しそうなクラスを発見しました。

https://learn.microsoft.com/en-us/dotnet/api/Microsoft.OpenApi.Models.OpenApiInfo

こいつのプロパティ `Title`, `Description`, `Version` を設定して適用させればよさそうです。

おっと、全文は読んでませんが、公式でサンプルを発見しました。

```cs
namespace MyFunctionApp
{
    public class Program
    {
        public static void Main()
        {
            var host = new HostBuilder()
                .ConfigureFunctionsWorkerDefaults(worker => worker.UseNewtonsoftJson())
                /* ⬇️⬇️⬇️ Add this ⬇️⬇️⬇️ */
                .ConfigureServices(services =>
                {
                    services.AddSingleton<IOpenApiConfigurationOptions>(_ =>
                            {
                                var options = new OpenApiConfigurationOptions()
                                {
                                    Info = new OpenApiInfo()
                                    {
                                        Version = "1.0.0",
                                        Title = "Swagger Petstore",
                                        Description = "This is a sample server Petstore API designed by [http://swagger.io](http://swagger.io).",
                                        TermsOfService = new Uri("https://github.com/Azure/azure-functions-openapi-extension"),
                                        Contact = new OpenApiContact()
                                        {
                                            Name = "Enquiry",
                                            Email = "azfunc-openapi@microsoft.com",
                                            Url = new Uri("https://github.com/Azure/azure-functions-openapi-extension/issues"),
                                        },
                                        License = new OpenApiLicense()
                                        {
                                            Name = "MIT",
                                            Url = new Uri("http://opensource.org/licenses/MIT"),
                                        }
                                    },
                                    Servers = DefaultOpenApiConfigurationOptions.GetHostNames(),
                                    OpenApiVersion = OpenApiVersionType.V2,
                                    IncludeRequestingHostName = true,
                                    ForceHttps = false,
                                    ForceHttp = false,
                                };

                                return options;
                            });
                })
                /* ⬆️⬆️⬆️ Add this ⬆️⬆️⬆️ */
                .Build();

            host.Run();
        }
    }
}
```

https://github.com/Azure/azure-functions-openapi-extension/blob/main/docs/openapi-out-of-proc.md#injecting-openapiconfigurationoptions-during-startup

OpenApiInfo の必要なプロパティだけ変更させていただきましょう。

```diff_c_sharp:Program.cs
+using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions;
+using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

IHost host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services =>
    {
        services.AddApplicationInsightsTelemetryWorkerService();
        services.ConfigureFunctionsApplicationInsights();

+        OpenApiInfo info = new()
+        {
+            Title = "My API",
+            Description = "This is my API.",
+            Version = "1.1.0",
+        };
+
+        services.AddSingleton<IOpenApiConfigurationOptions>(_ =>
+        {
+            OpenApiConfigurationOptions options = new()
+            {
+                Info = info,
+            };
+
+            return options;
+        });
    })
    .Build();

host.Run();

```

## 固定文字列で変更できた

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/ab6820ad-997f-5571-d485-8945ac6b6f50.png)

よし、変更できました。

## バージョン文字列を自動更新
あとはバージョンをアセンブリバージョンから取得してみましょう。

```diff_c_sharp
+using System.Reflection;

:

+        // 現在のアセンブリのバージョン情報を取得
+        Version? version = Assembly.GetExecutingAssembly().GetName().Version;

        // OpenAPI のカスタマイズ
        OpenApiInfo info = new()
        {
            Title = "My API",
            Description = "This is my API.",
            Version = "1.1.0",
+            Version = $"{version}",
        };
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/5eeaeda9-58b0-eed3-5f61-0d934d0cbc2d.png)

### バージョンを変更してみると

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/a8b2ef19-5c97-8f41-a5a7-4a0b5a5905d3.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/50e75640-3acb-88d4-7004-b15ce8038c9e.png)

変わりました。これでアセンブリのバージョン（パッケージ バージョン）を設定することで自動的に Swagger UI に表示されるバージョンが更新されます。

最終的に `Program.cs` は以下のようになりました。

```cs
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Reflection;

IHost host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services =>
    {
        services.AddApplicationInsightsTelemetryWorkerService();
        services.ConfigureFunctionsApplicationInsights();

        // 現在のアセンブリのバージョン情報を取得
        Version? version = Assembly.GetExecutingAssembly().GetName().Version;

        // OpenAPI のカスタマイズ
        OpenApiInfo info = new()
        {
            Title = "My API",
            Description = "This is my API.",
            Version = $"{version}",
        };

        services.AddSingleton<IOpenApiConfigurationOptions>(_ =>
        {
            OpenApiConfigurationOptions options = new()
            {
                Info = info,
            };

            return options;
        });
    })
    .Build();

host.Run();

```