Azure Functions の分離ワーカーモデルで OpenAPI/Swagger を使う

:::note alert
~~2024/05/22 に公開された Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore バージョン 1.3.0 にすると Swagger が動かなくなります！~~
:::

:::note info
2024/06/14 に公開された Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore バージョン 1.3.2 で動作することを確認しました。
Microsoft.Azure.Functions.Worker.Extensions.OpenApi のバージョンは 1.5.1 です。
:::

# やりたいこと
- Azure Functions の分離ワーカーモデルで OpenAPI/Swagger を有効化した HTTP トリガー Functions を作りたい

# 環境
- Visual Studio Community 2022 17.9.6
- .NET 8(C#12)

# 背景
- .NET 6 の Azure Functions インプロセスモデルでは Function を作成する際に `Http trigger with OpenAPI` という選択肢があり、簡単に OpenAPI/Swagger を導入できた
- 分離ワーカーモデルでは `Http trigger with OpenAPI` の選択肢がなくなってしまった
- 分離ワーカーモデルでも Swagger を使いたい！

# インプロセスモデルを確認する

Azure Functions プロジェクトの作成時に Function で `Http trigger with OpenAPI` を選択できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/815a43ff-e123-f432-576e-0f09dd13d0d6.png)

これを選択すると、OpenAPI の NuGet パッケージが追加され、Function に OpenAPI 関連の Attribute が追加されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/a22a39dd-9f47-6c1a-8b9a-0afc624bd863.png)

```cs:Function1.cs
[FunctionName("Function1")]
[OpenApiOperation(operationId: "Run", tags: new[] { "name" })]
[OpenApiParameter(name: "name", In = ParameterLocation.Query, Required = true, Type = typeof(string), Description = "The **Name** parameter")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(string), Description = "The OK response")]
public async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req)
{
    _logger.LogInformation("C# HTTP trigger function processed a request.");

    string name = req.Query["name"];

    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    dynamic data = JsonConvert.DeserializeObject(requestBody);
    name = name ?? data?.name;

    string responseMessage = string.IsNullOrEmpty(name)
        ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
        : $"Hello, {name}. This HTTP triggered function executed successfully.";

    return new OkObjectResult(responseMessage);
}
```

デバッグ実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/e16dad83-33a6-a8ed-0c36-d97016279dfb.png)

`swagger/ui` にブラウザでアクセスします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/df9f4770-99b0-d54f-fe86-1878de4b34f9.png)

こいつを分離ワーカーモデルでも実現したい。

# .NET 8 分離ワーカーモデルを実行してみる

仕方がないので `Http trigger` を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/bc1e97bd-a360-cf52-8586-bfe954d7d942.png)

当然ですが、OpenAPI の NuGet パッケージは追加されていません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/31de8598-2737-6e1a-7ce9-e2b7a40332f6.png)

デバッグ実行してもそこにあるのは Function だけ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/2d2b2b56-400a-58f8-d040-80e472188e51.png)

Function も驚くほど簡素（笑）

```cs:Function1.cs
[Function("Function1")]
public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequest req)
{
    _logger.LogInformation("C# HTTP trigger function processed a request.");
    return new OkObjectResult("Welcome to Azure Functions!");
}
```

# .NET 8 分離ワーカーモデルに OpenAPI/Swagger を追加する

まずは NuGet パッケージを追加しましょう。分離ワーカー（the out-of-process worker）向けと思われるパッケージがありました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/03e92c31-55bd-bc8b-003f-634d5db01059.png)

デバッグ実行をすると早速 Swagger が現れました！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/ddd2f27b-86b0-8786-05f5-c9ebad18592a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/a36b7ea9-cdd2-2750-ae0d-e27e3c9646a7.png)

Attribute を追加します。

```diff_c_sharp:Function1.cs
[Function("Function1")]
+[OpenApiOperation(operationId: "Run", tags: ["name"])]
+[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(string), Description = "The OK response")]
public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequest req)
{
    _logger.LogInformation("C# HTTP trigger function processed a request.");
    return new OkObjectResult("Welcome to Azure Functions!");
}
```

Swagger に表示されました！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/8faf9f01-570e-5b94-5143-e0296c4ccb6e.png)

# まとめ

.NET 8 分離ワーカーモデルの Azure Functions でも .NET 6 インプロセスモデルと同様に Swagger を導入することができました。それにしてもなぜテンプレートで選べないんでしょうか。今後のアップデートに期待しておきます。