Docker 環境と C# ASP.NET Core で最小 API を実装する：MySQL

# Docker 環境と C# ASP.NET Core で最小 API を実装する：MySQL

こんにちは、@studio_meowtoon です。今回は、WSL の Ubuntu 24.04 で C# ASP.NET Core Web アプリケーションを作成し、最小限の REST API を実装する方法を紹介します。
![aspnet-core_and_mysql__on_docker.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/5c416cb7-5cbd-aa06-64e3-f91681f6014d.png)

## 目的

Windows 11 の Linux でクラウド開発します。

こちらから記事の一覧がご覧いただけます。

https://qiita.com/studio_meowtoon/items/6119ab05d8b13527efd8

## 実現すること

ローカル環境の Ubuntu の Docker 環境で、Dockerfile からビルドした C# ASP.NET Core Web サービスのカスタムコンテナーと MySQL データベースコンテナーを起動します。

**DLL ファイル形式のアプリをコンテナーとして起動**
```mermaid
flowchart LR
    terminal --> kestrel
    subgraph Ubuntu
        subgraph Docker
            subgraph app-container[api-todo-aspnet-core]
                subgraph dotnet
                    kestrel <--> app[WebApp.dll]
                end
            end
            subgraph db-container[mysql-todo]
                subgraph mysql
                    db_todo
                end
            end
            app[WebApp.dll] <--> db_todo
        end
    end
style Ubuntu fill:#FFBCA4, stroke:#E0460F
style Docker fill:#EEFDFF, stroke:#74B9DC
style dotnet fill:#DCFFDC, stroke:#2F872F
style app-container fill:#FFD7E4, stroke:#E13C73
style db-container fill:#FFD7E4, stroke:#E13C73
style app fill:#ECECFF, stroke:#9674DC
style kestrel fill:#FFFFDE, stroke:#B4AE43
style mysql fill:#FFFFDE, stroke:#B4AE43
```

**実行環境**

|要素|概要|
|:--|:--|
|terminal|ターミナル|
|Ubuntu|OS|
|Docker|コンテナー実行環境|

**API サービス コンテナー**

|要素|概要|
|:--|:--|
|api-todo-aspnet-core|API サービス カスタムコンテナー|
|dotnet|.NET 実行環境|
|kestrel|Web サーバー|
|WebApp.dll|.NET アプリケーション|

**データベース コンテナー**

|要素|概要|
|:--|:--|
|mysql-todo|データベースコンテナー|
|mysql|DB サーバー|
|db_todo|データベース|

:::note alert
C# には本来[厳格なコーディング規則](https://learn.microsoft.com/ja-jp/dotnet/csharp/fundamentals/coding-style/coding-conventions)がありますが、この記事では可読性のために、一部規則に沿わない表記方法を使用しています。ご注意ください。
:::

## 技術トピック

### ASP.NET Core とは？  

<details>
<summary>こちらを展開してご覧いただけます。</summary>

### [ASP.NET Core](https://learn.microsoft.com/ja-jp/aspnet/core/introduction-to-aspnet-core?view=aspnetcore-6.0)

:::note info
ASP.NET Core は、Microsoft が開発したオープンソースの Web アプリケーションフレームワークです。  

|キーワード|内容|
|:--|:--|
|クロスプラットフォームで動作します|ASP.NET Core は、Windows、Linux、macOS など、さまざまなオペレーティングシステム上で動作します。これにより、異なるプラットフォームで一貫性のあるアプリケーションを構築できます。|
|多様な用途に利用可能|ASP.NET Core は、Web アプリケーション、API、マイクロサービス、IoT アプリケーションなど、さまざまな用途に適しています。これにより、幅広いプロジェクトに対応できます。|
|軽量なリクエスト処理パイプライン|ASP.NET Core は、高性能を提供するために最適化された軽量なリクエスト処理パイプラインを備えています。これにより、リクエストの処理効率が向上し、レスポンスタイムが短縮されます。|
|依存関係注入|アプリケーション内のコンポーネント間の依存関係を注入する仕組みが組み込まれています。これにより、コードのテストや保守が容易になり、柔軟なアーキテクチャの構築が可能です。|
|テスト可能なアーキテクチャ|ASP.NET Core は、ユニットテストや統合テストをサポートするために設計されたアーキテクチャを提供します。これにより、品質の高いアプリケーションの開発が可能です。|
|多様な開発環境|Visual Studio、VS Code、その他のエディターを使用して ASP.NET Core アプリケーションを開発できます。これにより、開発者は自分の好みやニーズに合った環境で作業できます。|

:::
</details>

## 開発環境

+ Windows 11 Home 23H2 を使用しています。

:::note info
[WSL](https://learn.microsoft.com/ja-jp/windows/wsl/about) の Ubuntu を操作しますので macOS の方も参考にして頂けます。  
:::

<details>

WSL (Microsoft Store アプリ版) [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/cd0c4a607d451dbf5430)

```sh
> wsl --version
WSL バージョン: 2.2.4.0
カーネル バージョン: 5.15.153.1-2
WSLg バージョン: 1.0.61
```

Ubuntu [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/cd0c4a607d451dbf5430)

```sh
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 24.04 LTS
Release:        24.04
Codename:       noble
```

.NET SDK [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/d68031c91462f6808dff)

```sh
$ dotnet --list-sdks
8.0.107 [/usr/lib/dotnet/sdk]
$ dotnet --version
8.0.107
```

Docker [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/3bceee25ac67cc73ebbe)

```sh
$ docker --version
Docker version 27.0.3, build 7d4bcd8
```

</details>

:::note warn
この記事では基本的に Ubuntu のターミナルで操作を行います。**Vim を使用してコピペする方法**をはじめて学ぶ人のために、以下の記事で手順を紹介しています。ぜひ挑戦してみてください。
:::

https://qiita.com/studio_meowtoon/items/7771ad48676926e41463

## 作成する REST API の仕様

|エンドポイント|HTTPメソッド|説明|リクエストBody|レスポンスBody|
|:--|:--|:--|:--|:--|
|/todos         |GET   |すべての ToDo アイテムを取得します。|None         |ToDo アイテムの配列|
|/todos/complete|GET   |完了した ToDo アイテムを取得します。|None         |ToDo アイテムの配列|
|/todos/{id}    |GET   |ID で ToDo アイテムを取得します。   |None         |ToDo アイテム|
|/todos         |POST  |新しい ToDo アイテムを追加します。  |ToDo アイテム|ToDo アイテム|
|/todos/{id}    |PUT   |既存の ToDo アイテムを更新します。  |ToDo アイテム|None|
|/todos/{id}    |DELETE|ID で ToDo アイテムを削除します。   |None         |None|

## データベース コンテナーの起動

こちらの記事で、ToDo アプリ用の RDBMS データベースを作成し、Docker コンテナーとして起動する手順をご確認いただけます。

https://qiita.com/studio_meowtoon/items/ce1c853893388c106d22

データベース コンテナーが起動していることを確認します。

```sh
$ docker start mysql-todo
$ docker ps
CONTAINER ID   IMAGE        COMMAND                   CREATED      STATUS         PORTS                                                  NAMES
5eb600ed3d54   mysql-base   "docker-entrypoint.s…"   5 days ago   Up 9 seconds   0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp   mysql-todo
```

:::note alert
コンテナー間通信するために **net-todo** という Docker ネットワークをあらかじめ作成しています。ご注意ください。
:::

## REST API を実装する手順

### プロジェクトルートフォルダーに移動

プロジェクトルートフォルダーに移動します。  
※ ~/tmp/restapi-aspnet-core をプロジェクトフォルダーとします。

```sh
$ cd ~/tmp
```

### プロジェクトの作成

プロジェクトを作成します。

```sh
$ dotnet new web -n restapi-aspnet-core
```

プロジェクトフォルダーに移動します。

```sh
$ cd ~/tmp/restapi-aspnet-core
```

### csproj ファイルのリネーム

:::note alert
こちらの作業は最終的にアプリを **WebApp.dll** という名前でビルドするために行っています。デフォルトの名前で問題ない場合にはそのまま読み進めてください。
:::

```sh
$ mv restapi-aspnet-core.csproj WebApp.csproj
```

### ライブラリの追加

Entity Framework Core パッケージを [**NuGet**](https://learn.microsoft.com/ja-jp/nuget/what-is-nuget) で取得します。

```sh
$ dotnet add package MySql.EntityFrameworkCore
```

:::note warn
この記事ではデータベースに MySQL を使用する為、MySQL 用のライブラリを取得しています。
:::

### Model クラスの作成

:::note alert
このシリーズの記事で、**RDBMS** と **NoSQL** の両方で**同じ API 操作**を行います。そのため、ID フィールドは数値型ではなく文字列として定義しています。統一性を保ちつつ異なるデータストアで動作することを目指しています。ご了承ください。
:::

Model クラスを作成します。

```sh
$ vim Model.cs
```

ファイルの内容

```cs:Model.cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using static System.Environment;

using Microsoft.EntityFrameworkCore;

// ToDo エンティティに対する DbContext クラス
public class TodoDb : DbContext {
    public DbSet<Todo> Todos => Set<Todo>();
    public TodoDb(DbContextOptions<TodoDb> options) : base(options) { }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
        optionsBuilder.UseMySQL(
            $"server={GetEnvironmentVariable("DB_HOST")}; " +
            $"port={GetEnvironmentVariable("DB_PORT")}; " +
            $"database={GetEnvironmentVariable("DB_NAME")}; " +
            $"user={GetEnvironmentVariable("DB_USER")}; " +
            $"password={GetEnvironmentVariable("DB_PASSWORD")}"
        );
    }
}

// ToDo エンティティを表すクラス
[Table("todos")]
public class Todo {
    // RDBMS 本来の int 型のキー：API 入出力の JSON にはマッピングしない
    [JsonIgnore]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Key][Column("id")] public int _rdbms_id { get; set; }

    // string 型の ID を定義：RDBMS 側にはマッピングしない
    [NotMapped] public string id { 
        get { return _rdbms_id.ToString(); } 
        set { _rdbms_id = int.Parse(value); } 
    }

    [Column("content")] public string? content { get; set; }
    [Column("created_date")] public DateTime? created_date { get; set; }
    [Column("completed_date")] public DateTime? completed_date { get; set; }
}
```

<details>
<summary>説明を開きます。</summary>

**TodoDb クラス**

|要素|説明|
|:--|:--|
|DbContext|DbContext クラスを継承しています。これは Entity Framework Core のコンテキストクラスであり、データベースとの接続やクエリの実行を行います。|
|DbSet\<Todo\>|Todos プロパティは DbSet\<Todo\> 型のプロパティです。これは、エンティティセットとして操作したいエンティティのコレクションを表します。|
|OnConfiguring|コンストラクター内で、データベース接続情報を設定しています。OnConfiguring メソッドで MySQL データベースの接続文字列を構築しています。|

**Todo クラス**

|要素|説明|
|:--|:--|
|Todo|ToDo アイテムを表すエンティティクラスです。|
|Table("todos")|このクラスが "todos" テーブルとマッピングされることを示す属性です。|
|DatabaseGenerated|_rdbms_id プロパティが自動生成されることを示しています。これにより、データベースの主キーが自動的に生成されます。|
|Key|エンティティの主キーを指定するための属性です。|
|Column("id")|データベーステーブルの列とエンティティプロパティをマッピングするための属性です。|
|JsonIgnore|_rdbms_id プロパティが JSON シリアライズされないようにしています。これにより、API 入出力時に ID が表示されなくなります。|
|NotMapped|JSON シリアライズ時に _rdbms_id プロパティを文字列として表現するためのプロパティです。データベースにはマッピングされません。|
|id|_rdbms_id プロパティを介してマッピングされる、エンティティの識別子を表します。JSON シリアライゼーション時には無視されます。|
|_rdbms_id|RDBMS 本来の int 型のキーとして使用され、JSON シリアライズ時には無視されます。|
|content、created_date、completed_date|それぞれ ToDo アイテムの内容、作成日時、完了日時を表します。|

</details>

### Program クラスの修正

Program クラスを修正します。

```sh
$ vim Program.cs
```

ファイルの内容

```csharp:Program.cs
using Microsoft.EntityFrameworkCore;

// app オブジェクトを作成します。
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors();
builder.Services.AddDbContext<TodoDb>();
WebApplication app = builder.Build();

// CORS 設定：適切に修正してください。
app.UseCors(builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());

// ルートグループを作成します。
RouteGroupBuilder routeGroupTodos = app.MapGroup("/todos");

// すべての ToDo アイテムを取得します。
routeGroupTodos.MapGet("/", getAllTodos);
static async Task<IResult> getAllTodos(TodoDb db) {
    return TypedResults.Ok(
        await db.Todos.ToArrayAsync()
    );
}

// 完了した ToDo アイテムを取得します。
routeGroupTodos.MapGet("/complete", getCompleteTodos);
static async Task<IResult> getCompleteTodos(TodoDb db) {
    return TypedResults.Ok(
        await db.Todos.Where(x => x.completed_date != null).ToListAsync()
    );
}

// ID で ToDo アイテムを取得します。
routeGroupTodos.MapGet("/{id}", getTodo);
static async Task<IResult> getTodo(string id, TodoDb db) {
    return await db.Todos.FindAsync(int.Parse(id))
        is Todo todo
            ? TypedResults.Ok(todo)
            : TypedResults.NotFound();
}

// 新しい ToDo アイテムを追加します。
routeGroupTodos.MapPost("/", createTodo);
static async Task<IResult> createTodo(Todo todo, TodoDb db) {
    todo.created_date = DateTime.Now;
    db.Todos.Add(todo);
    await db.SaveChangesAsync();
    return TypedResults.Created($"/todos/{todo.id}", todo);
}

// 既存の ToDo アイテムを更新します。
routeGroupTodos.MapPut("/{id}", updateTodo);
static async Task<IResult> updateTodo(string id, Todo todo, TodoDb db) {
    Todo? target = await db.Todos.FindAsync(int.Parse(id));
    if (target is null) {
        return TypedResults.NotFound();
    }
    target.content = todo.content;
    target.completed_date = todo.completed_date;
    await db.SaveChangesAsync();
    return TypedResults.NoContent();
}

// ID で ToDo アイテムを削除します。
routeGroupTodos.MapDelete("/{id}", deleteTodo);
static async Task<IResult> deleteTodo(string id, TodoDb db) {
    if (await db.Todos.FindAsync(int.Parse(id)) is Todo todo) {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return TypedResults.NoContent();
    }
    return TypedResults.NotFound();
}

app.Run();
```

<details>
<summary>説明を開きます。</summary>

|要素|説明|
|:--|:--|
|Task\<IResult\>|非同期操作の結果を表す型で、非同期メソッドの戻り値として使用されます。|
|IResult|API エンドポイントの処理結果を表すインターフェイスです。成功や失敗などの状態を持ち、HTTP レスポンスを適切に生成するための情報を提供します。|
|TypedResults|API エンドポイントのレスポンスを構築するためのヘルパークラスです。TypedResults.Ok(data) は成功した場合のレスポンスを生成し、TypedResults.NotFound() はリソースが見つからなかった場合のレスポンスを生成するなど、一般的な API レスポンスを簡単に生成できます。|
|Cors 設定|app.UseCors(...) で定義されており、異なるドメイン間のリクエストを許可するための設定です。AllowAnyOrigin() はどのオリジンからのリクエストでも許可することを意味し、AllowAnyHeader() と AllowAnyMethod() はすべてのヘッダーと HTTP メソッドを許可することを意味します。|

</details>

### アプリのビルドと起動

環境変数を作成します。

```sh
export DB_HOST=localhost
export DB_PORT=3306
export DB_NAME=db_todo
export DB_USER=root
export DB_PASSWORD=password
```

:::note alert
この環境変数が**一時的なもの**であることに注意してください。
:::

アプリを起動します。
※ アプリを停止するときは ctrl + C を押します。

:::note warn
この例では、アプリがリッスンする**ポート番号**を**5000**に設定して起動しています。
:::

```sh
$ dotnet run --urls http://localhost:5000
```

:::note info
ここまでの手順で、**Ubuntu** でアプリの **DLL ファイル**を起動することができました。
:::

### アプリの動作確認

別ターミナルから curl コマンドで確認します。

:::note warn
必要な場合、jq をインストールします。

```sh
$ sudo apt update
$ sudo apt install jq
```

:::

#### GET: /todos エンドポイントの動作確認

すべての ToDo アイテムを取得します。

```sh
$ curl -s http://localhost:5000/todos | jq '.'
```

レスポンス

```sh
[
// 省略
  {
    "id": "3",
    "content": "運動する",
    "created_date": "2024-08-11T11:02:02Z",
    "completed_date": "2024-08-11T11:02:02Z"
  },
  {
    "id": "4",
    "content": "本を読む",
    "created_date": "2024-08-11T11:02:02Z",
    "completed_date": null
  },
  {
    "id": "5",
    "content": "請求書を支払う",
    "created_date": "2024-08-11T11:02:02Z",
    "completed_date": null
  },
// 省略
```

:::note info
すべての ToDo アイテムを取得できています。
:::

#### GET: /todos/complete エンドポイントの動作確認

完了した ToDo アイテムを取得します。

```sh
$ curl -s http://localhost:5000/todos/complete | jq '.'
```

レスポンス

```sh
[
  {
    "id": "1",
    "content": "食材を買う",
    "created_date": "2024-08-11T11:02:02Z",
    "completed_date": "2024-08-11T11:02:02Z"
  },
  {
    "id": "2",
    "content": "報告書を仕上げる",
    "created_date": "2024-08-11T11:02:02Z",
    "completed_date": "2024-08-11T11:02:02Z"
  },
  {
    "id": "3",
    "content": "運動する",
    "created_date": "2024-08-11T11:02:02Z",
    "completed_date": "2024-08-11T11:02:02Z"
  }
]
```

:::note info
完了した ToDo アイテムを取得できています。
:::

#### GET: /todos/{id} エンドポイントの動作確認

ID で ToDo アイテムを取得します。

```sh
$ curl -s http://localhost:5000/todos/8 | jq '.'
```

レスポンス

```sh
{
  "id": "8",
  "content": "コードを書く",
  "created_date": "2024-08-11T11:02:02Z",
  "completed_date": null
}
```

:::note info
ID で ToDo アイテムを取得できています。
:::

#### POST: /todos エンドポイントの動作確認

新しい ToDo アイテムを追加します。

```sh
$ curl -s -X POST http://localhost:5000/todos \
    -H 'Content-Type: application/json; charset=utf-8' \
    -d \
'{
    "content": "昼寝をする"
}' | jq '.'
```

レスポンス

```sh
{
  "id": "9",
  "content": "昼寝をする",
  "created_date": "2024-08-17T10:09:21.6968925+09:00",
  "completed_date": null
}
```

:::note info
新しい ToDo アイテムが追加できています。
:::

#### PUT: /todos/{id} エンドポイントの動作確認

既存の ToDo アイテムを更新します。

```sh
$ curl -s -X PUT http://localhost:5000/todos/9 \
    -H 'Content-Type: application/json; charset=utf-8' \
    -d \
'{
    "content": "窓を開ける"
}' | jq '.'
```

レスポンス

```sh
※なし
```

:::note warn
レスポンスは None ですが、DB のテーブルを確認すると対象のレコードが更新されています。
:::

#### DELETE: /todos/{id} エンドポイントの動作確認

ID で ToDo アイテムを削除します。

```sh
$ curl -s -X DELETE http://localhost:5000/todos/9 | jq '.'
```

レスポンス

```sh
※なし
```

:::note warn
レスポンスはなしですが、DB のテーブルを確認すると対象のレコードが削除されています。
:::

:::note info
ここまでの手順で、最小限の **CRUD 操作**を行う **REST API** をアプリに実装できました。
:::

### コンテナーイメージの作成

Dockerfile を作成します。

```sh
$ vim Dockerfile
```

ファイルの内容

```Dockerfile:Dockerfile
# build the app.
FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim AS build-env

# set the working dir.
WORKDIR /App

# copy source code to the working dir.
COPY . .

# restore as distinct layers.
RUN dotnet restore WebApp.csproj

# build and publish the app as a release.
RUN dotnet publish WebApp.csproj -c Release -o out

# set up the container.
FROM mcr.microsoft.com/dotnet/aspnet:8.0-bookworm-slim

# set the working dir.
WORKDIR /App

# copy the built app from the build-env.
COPY --from=build-env /App/out .

# command to run the app.
ENTRYPOINT ["dotnet","WebApp.dll"]
```

<details>
<summary>説明を開きます。</summary>

|要素|説明|
|:--|:--|
|FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim AS build-env|ビルド環境として .NET SDK イメージを使用します。|
|WORKDIR /App|作業ディレクトリを /App に設定します。|
|COPY . .|ソースコードを作業ディレクトリにコピーします。|
|RUN dotnet restore WebApp.csproj|プロジェクトの依存関係を復元します。|
|RUN dotnet publish WebApp.csproj -c Release -o out|プロジェクトをリリースモードでビルド・パブリッシュします。|
|FROM mcr.microsoft.com/dotnet/aspnet:8.0-bookworm-slim|実行環境として ASP.NET Core ランタイムイメージを使用します。|
|WORKDIR /App|作業ディレクトリを /App に設定します。|
|COPY --from=build-env /App/out .|ビルド環境からビルドされたアプリケーションをコピーします。|
|ENTRYPOINT ["dotnet","WebApp.dll"]|コンテナーが起動されたときに実行されるコマンドを設定します。|

</details>

Docker デーモンを起動します。

```sh
$ sudo service docker start
```

:::note warn
Docker 環境をお持ちでない場合は、以下の関連記事から Docker Engine のインストール手順をご確認いただけます。
:::

https://qiita.com/studio_meowtoon/items/3bceee25ac67cc73ebbe

コンテナーイメージをビルドします。

```sh
$ docker build \
    --no-cache \
    --tag api-todo-aspnet-core:latest .
```

コンテナーイメージを確認します。

```sh
$ docker images | grep api-todo-aspnet-core
api-todo-aspnet-core    latest    2909be24e19b    13 seconds ago   235MB
```

:::note info
ここまでの手順で、ローカル環境の **Docker** にアプリの**カスタムコンテナーイメージ**をビルドすることができました。
:::

### コンテナーを起動

ローカルでコンテナーを起動します。
※ コンテナーを停止するときは ctrl + C を押します。

:::note alert
コンテナー間通信するために **net-todo** という Docker ネットワークをあらかじめ作成しています。ご注意ください。
:::

```sh
$ docker run --rm \
    --publish 5000:8080 \
    --name api-local \
    --net net-todo \
    --env DB_HOST=mysql-todo \
    --env DB_PORT=3306 \
    --env DB_NAME=db_todo \
    --env DB_USER=root \
    --env DB_PASSWORD=password \
    api-todo-aspnet-core
```

:::note info
ここまでの手順で、ローカル環境の **Docker** でアプリの**カスタムコンテナー**を起動することができました。
:::

### コンテナーの動作確認

別ターミナルから curl コマンドで確認します。
※ ID で ToDo アイテムを取得します。

```sh
$ curl -s http://localhost:5000/todos/8 | jq '.'
```

レスポンス

```sh
{
  "id": "8",
  "content": "コードを書く",
  "created_date": "2024-08-11T11:02:02Z",
  "completed_date": null
}
```

:::note info
ここまでの手順で、ターミナルにレスポンスが表示され、**JSON データ**を取得することができました。
:::

コンテナーの状態を確認してみます。

```sh
$ docker ps
CONTAINER ID   IMAGE                  COMMAND                   CREATED              STATUS              PORTS
    NAMES
f9861416969b   api-todo-aspnet-core   "dotnet WebApp.dll"       About a minute ago   Up About a minute   0.0.0.0:5000->8080/tcp, :::5000->8080/tcp
    api-local
5eb600ed3d54   mysql-base             "docker-entrypoint.s…"   5 days ago           Up 37 minutes       0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp   mysql-todo
```

### コンテナーに接続

別ターミナルからコンテナーに接続します。

```sh
$ docker exec -it api-local /bin/bash
```

コンテナー接続後にディレクトリを確認します。  
※ コンテナーから出るときは ctrl + D を押します。

```
# pwd
/App
# ls -lah
total 15M
drwxr-xr-x 1 root root 4.0K Aug 17 01:13 .
drwxr-xr-x 1 root root 4.0K Aug 17 01:17 ..
-rwxr--r-- 1 root root 6.9M May  7 16:05 BouncyCastle.Cryptography.dll
-rwxr--r-- 1 root root 463K Mar 27 18:10 Google.Protobuf.dll
-rwxr--r-- 1 root root  83K Mar 17 21:56 K4os.Compression.LZ4.Streams.dll
-rwxr--r-- 1 root root  69K Mar 17 21:56 K4os.Compression.LZ4.dll
-rwxr--r-- 1 root root  13K Nov  8  2022 K4os.Hash.xxHash.dll
-rwxr--r-- 1 root root  34K Apr 17 18:38 Microsoft.EntityFrameworkCore.Abstractions.dll
-rwxr--r-- 1 root root 1.9M Apr 17 18:39 Microsoft.EntityFrameworkCore.Relational.dll
-rwxr--r-- 1 root root 2.5M Apr 17 18:39 Microsoft.EntityFrameworkCore.dll
-rwxr--r-- 1 root root 1.2M Jun  7 14:59 MySql.Data.dll
-rwxr--r-- 1 root root 242K Jun  7 15:17 MySql.EntityFrameworkCore.dll
-rwxr--r-- 1 root root 433K Oct 31  2023 System.Configuration.ConfigurationManager.dll
-rwxr--r-- 1 root root  36K Oct 31  2023 System.Security.Cryptography.ProtectedData.dll
-rwxr--r-- 1 root root 112K Oct 31  2023 System.Security.Permissions.dll
-rwxr--r-- 1 root root  27K Oct 31  2023 System.Windows.Extensions.dll
-rwxr-xr-x 1 root root  71K Aug 17 01:13 WebApp
-rw-r--r-- 1 root root  25K Aug 17 01:13 WebApp.deps.json
-rw-r--r-- 1 root root  16K Aug 17 01:13 WebApp.dll
-rw-r--r-- 1 root root  23K Aug 17 01:13 WebApp.pdb
-rw-r--r-- 1 root root  537 Aug 17 01:13 WebApp.runtimeconfig.json
-rwxr--r-- 1 root root 402K Apr 24 18:36 ZstdSharp.dll
-rw-r--r-- 1 root root  119 Aug 17 00:41 appsettings.Development.json
-rw-r--r-- 1 root root  142 Aug 17 00:41 appsettings.json
drwxr-xr-x 4 root root 4.0K Aug 17 01:13 runtimes
-rw-r--r-- 1 root root  482 Aug 17 01:13 web.config
```

top コマンドで状況を確認します。

```
# apt update
# apt install procps
# top
top - 01:19:47 up  1:00,  0 user,  load average: 0.09, 0.05, 0.01
Tasks:   3 total,   1 running,   2 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.1 sy,  0.0 ni, 99.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :  15949.2 total,  14445.3 free,   1296.6 used,    515.8 buff/cache
MiB Swap:   4096.0 total,   4096.0 free,      0.0 used.  14652.6 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
      1 root      20   0  262.3g 127728 103420 S   0.0   0.8   0:01.58 dotnet
     44 root      20   0    4188   3320   2812 S   0.0   0.0   0:00.02 bash
    235 root      20   0    8560   4560   2688 R   0.0   0.0   0:00.00 top
```

コンテナーの情報を表示してみます。

```cs
# cat /etc/*-release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
```

:::note warn
このコンテナーは Debian GNU/Linux をベースに作成されています。つまり、Debian GNU/Linux と同じように扱うことができます。
:::

## おまけ：Swagger 装備

### ライブラリの追加

Swagger 関連パッケージを [**NuGet**](https://learn.microsoft.com/ja-jp/nuget/what-is-nuget) で取得します。

```sh
$ dotnet add package Swashbuckle.AspNetCore
$ dotnet add package Microsoft.AspNetCore.OpenApi
```

### Program クラスの修正

Program クラスを修正します。

```sh
$ vim Program.cs
```

ファイルの内容

```diff_c_sharp:Program.cs ※一部分
using Microsoft.EntityFrameworkCore;

// app オブジェクトを作成します。
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors();
builder.Services.AddDbContext<TodoDb>();
+ builder.Services.AddEndpointsApiExplorer();
+ builder.Services.AddSwaggerGen();
WebApplication app = builder.Build();

// CORS 設定：適切に修正してください。
app.UseCors(builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());

+ // Swagger 設定
+ app.UseSwagger();
+ app.UseSwaggerUI();

// 以前のコード

app.Run();
```

### Swagger を確認

Web ブラウザで Swagger の URL を確認します。

```sh
http://localhost:5000/Swagger
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/7a03c222-98e4-a3a3-3141-63319e6749dd.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/830b9017-a5d8-4334-0af8-b1d21cdc2a2e.png)

:::note info
これらのツールを活用することにより、より手軽に API を操作できます。
:::

## まとめ

WSL Ubuntu の Docker 環境で、C# ASP.NET Core の最小 API を実装することができました。

:::note alert
この記事の実装例は1つのアプローチに過ぎず、必ずしも正しい方法とは限りません。他にも多様な方法がありますので、さまざまな情報を照らし合わせて検討してみてください。
:::

どうでしたか？　WSL Ubuntu で、C# ASP.NET Core Web アプリケーションを手軽に起動できます。ぜひお試しください。今後も .NET の開発環境などを紹介しますので、ぜひお楽しみにしてください。

## 推奨コンテンツ

https://qiita.com/studio_meowtoon/items/9bbf97d2e0668f157bb7

https://qiita.com/studio_meowtoon/items/ce1c853893388c106d22