【Visual Studio2022】.NETのプロジェクトをDockerで開発出来るようにする

<h1>概要</h1>
Visual Studio2022で、Dockerサポート機能を使って、開発出来るようにする手順です。<br>実行する環境は以下の通りです。

<br>OS：Windows11<br>プロジェクト：.NET Core Razor Pages<br>DB：MSSQL2019<br>Dockerバージョン：4.9.0

設定ファイルなどに含まれる、プロジェクト名などは、適宜書き換えてください。

<h1>プロジェクトをDockerコンテナ上で実行する</h1>

<h3>手順１. Dockerをインストールする</h3>
まずは、Dockerを公式サイトからインストールします。

https://www.docker.com/get-started/

<h3>手順２. プロジェクトの作成</h3>

新規プロジェクトを作成します。<br>今回はRazor Pageを選択して、プロジェクト名は「RazorPageSample」で作成します。
![画像1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1893891/f357a1cb-3f6b-4e9e-a9c9-482abbba6df5.png)

画面を進めていくと、追加情報という画面が表示されます。<br>「Dockerを有効にする」という項目に、チェックを入れて、OSはLinuxを選択しておきます。
![画像2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1893891/e6538b9d-d5eb-f53f-1cfb-7c93796899d4.png)

作成が完了すると、デバッグ開始ボタンがDockerになっています。<br>Dockerfileは自動で生成されるので、このまま実行すれば、Dockerコンテナ上で実行され、いつも通りデバッグ可能な状態になります。
![画像3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1893891/6ec959d7-bae7-f8ab-61dd-8ef88af99413.png)

Dockerに関する操作をすることなく、簡単に設定出来てしまうため、本当にDockerコンテナ上で動いているか、OSバージョンを表示させて確認してみます。

```html:Index.cshtml
@page
@model IndexModel
@{
    // OSの情報を取得する
    System.OperatingSystem os = System.Environment.OSVersion;
    ViewData["OS"] = os.ToString();

    ViewData["Title"] = "Home page";
}

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p class="display-6">OS：@ViewData["OS"]</p>
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
```

![画像4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1893891/fa9b94af-f418-f80f-8b6c-4c58313724dd.png)

.net6.0のイメージに使われているOSが、Debainになるので、Unixと表示されます。
以上の設定で、DBを使わないWebアプリなどであれば、簡単にDocker上で動かすことが出来ます。

<h3>補足</h3>
Dockerfileに下記の設定を追加すると、DateTimeで取得できる日時を、JSTのYYYY/MM/DD形式に変更できます。<br>.net6.0のイメージに使われているOSのタイムゾーンをJSTにして、言語を日本語にしています。

```dockerfile:
ENV TZ Asia/Tokyo
ENV DEBIAN_FRONTEND noninteractive
ENV LC_ALL ja_JP.UTF-8

RUN apt-get update
RUN apt-get install -y locales
RUN echo "ja_JP.UTF-8 UTF-8" > /etc/locale.gen && \
    locale-gen ja_JP.UTF-8 && \
    dpkg-reconfigure locales && \
    /usr/sbin/update-locale LANG=ja_JP.UTF-8
```

```dockerfile:Dockerfile
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base

# .net6.0のイメージに使われているOSがDebainで、以下の設定でタイムゾーンをJSTに変更して、日本語化している
ENV TZ Asia/Tokyo
ENV DEBIAN_FRONTEND noninteractive
ENV LC_ALL ja_JP.UTF-8

RUN apt-get update
RUN apt-get install -y locales
RUN echo "ja_JP.UTF-8 UTF-8" > /etc/locale.gen && \
    locale-gen ja_JP.UTF-8 && \
    dpkg-reconfigure locales && \
    /usr/sbin/update-locale LANG=ja_JP.UTF-8

WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["RazorPageSample/RazorPageSample.csproj", "RazorPageSample/"]
RUN dotnet restore "RazorPageSample/RazorPageSample.csproj"
COPY . .
WORKDIR "/src/RazorPageSample"
RUN dotnet build "RazorPageSample.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "RazorPageSample.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "RazorPageSample.dll"]
```
![画像15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1893891/3ee1aef8-81ba-5715-d0e5-fd263b1f26c2.png)

<h1>プロジェクトのコンテナとDBコンテナを同時に立ち上げる</h1>
次に、プロジェクトのコンテナとDBコンテナを、同時に立ち上げる設定方法です。<br>大抵の場合は、DBが必要になってくると思うので、こちらの設定が必要になるかと思います。<br>

<h3>手順１. コンテナーオーケストレーターサポートの追加</h3>
プロジェクトを右クリックして、追加からコンテナオーケストレーターサポートを追加をします。<br>コンテナーオーケストレーターは「Docker Compose」、ターゲットOSは、「Linux」を選択します。

![画像5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1893891/f2c2ea02-fe93-d064-3955-0f47b96a8286.png)

<h3>手順２. docker-compose.ymlにDBコンテナの設定を追加</h3>
ソリューションエクスプローラーにdocker-compose.ymlというファイルが追加されます。<br>このファイルは、複数のコンテナを同時に立ち上げるのに使うファイルです。<br>デフォルトでは、プロジェクトのコンテナのみになっているので、DBコンテナも立ち上げるように設定を追加します。

```yml:docker-compose.yml
version: '3.4'

services:
  razorpagesample:
    image: ${DOCKER_REGISTRY-}razorpagesample
    build:
      context: .
      dockerfile: RazorPageSample/Dockerfile
    depends_on:
      - sqlexpress

  sqlexpress:
    image: mcr.microsoft.com/mssql/server:2019-latest
    environment:
        MSSQL_PID: "Express"
        ACCEPT_EULA: "Y"
        SA_PASSWORD: "P@ssw0rd"
        MSSQL_LCID: "1041"
        MSSQL_COLLATION: "Japanese_CI_AS"
    volumes:
        - "./db/mssqlserver/data:/var/opt/mssql/data"
        - "./db/mssqlserver/log:/var/opt/mssql/log"
        - "./db/mssqlserver/secrets:/var/opt/mssql/secrets"
        - "./db/mssqlserver/backup:/var/opt/mssql/backup"
    ports:
        - "1401:1433"
```

depends_on: - sqlexpressで、先にDBコンテナを立ち上げてから、プロジェクトのコンテナを立ち上げるようにしています。<br>あとは、パスワードを設定したり、日本語化設定を行っています。<br>volumes:の設定はDBの永続化設定になります。<br>こうすることで、DBコンテナが終了しても、DBのデータが消えないようにしています。dbフォルダはプロジェクトの直下に生成されます。

<h3>手順３. SSMSからDBコンテナにアクセスする</h3>
開始ボタンが、「Docker」から「Docker Compose」に変わっていると思います。<br>Dockerは、Dockerfileを元に実行されますが、Docker Composeの場合は、docker-compose.ymlを元にコンテナが実行されます。

![画像7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1893891/bc748930-4d8d-cf8e-2a96-e5da4e514ae4.png)


<br>一度、Docker Composeで実行します。<br>すると、プロジェクトのコンテナと、DBコンテナが立ち上がります。<br>コンテナの状況は、Docker DesktopのContainersから確認出来ます。
![画像6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1893891/a806d658-1ed8-2e23-050f-1ba359b7a770.png)

DBコンテナが起動していることを確認して、SSMSからDBコンテナにアクセスしてみます。
接続先情報は、先ほどdocker-compose.ymlで設定した通りです。
![画像8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1893891/1386c7dc-ba59-5b3a-6061-972b7212662f.png)
![画像9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1893891/b12011df-8441-e961-23e2-8373222c764b.png)

アクセス出来ました。
これで、Docker上で開発出来る環境が整ったので、試しに公式のチュートリアルを進めてみます。


<h3>手順４. ムービーモデルの作成</h3>
まずは、Razor Pagesの公式チュートリアルのムービー モデルのスキャフォールディングまで進めます。

https://docs.microsoft.com/ja-jp/aspnet/core/tutorials/razor-pages/model?view=aspnetcore-6.0&tabs=visual-studio

<h3>手順５. DBコンテナに対して、EF の移行機能を使用して初期データベース スキーマを作成する</h3>
appsettings.jsonのDB接続先を、ローカルDBからDBコンテナに変更します。

```json:appsettings.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "RazorPageSampleContext": "Server=localhost,1401;Database=razor_sample_db;User ID=sa;Password=P@ssw0rd;initial catalog=razor_sample_db;MultipleActiveResultSets=True;App=EntityFramework;"
  }
}
```
<br>
<strong>スタートアッププロジェクトを「docker-compose」から「RazorPageSample」に切り替えてから</strong>、パッケージマネージャーコンソールで下記コマンドを実行します。<br>スタートアッププロジェクトを切り替えておかないと、コマンドが実行出来ません。

![画像10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1893891/a2bafe66-b61b-3eb4-c419-7ab8e93be182.png)

```powershell:
Add-Migration InitialCreate
Update-Database
```

<br>
コマンドが実行され、DBコンテナに初期データベースが作成されました。

![画像11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1893891/bf4b3de1-5bc4-ccb8-9b39-91daed12ba08.png)

<h3>手順６. appsettings.jsonを書き換える設定を追加</h3>
appsettings.jsonの接続先情報で、Docker Composeで実行すると、コンテナ自身のlocalhostを参照してしまい、DB接続エラーになってしまいます。<br>
なので、ソリューションエクスプローラーから、docker-compose.ymlの下にある「docker-compose.override.yml」というファイルに、下記の通り設定して接続出来るようにします。

```yaml:docker-compose.override.yml
version: '3.4'

services:
  razorpagesample:
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_URLS=https://+:443;http://+:80
      - ConnectionStrings__RazorPageSampleContext=Server=sqlexpress;Database=razor_sample_db;User ID=sa;Password=P@ssw0rd;initial catalog=razor_sample_db;MultipleActiveResultSets=True;App=EntityFramework;
    ports:
      - "80"
      - "443"
    volumes:
      - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
      - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro
```

実行するときに、appsettings.jsonのConnectionStrings > RazorPageSampleContextの値が、docker-compose.override.ymlで設定した内容に書き換わるようになります。

これで、Dockerコンテナ上で、データの登録、編集、削除が出来るようになります。
![画像12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1893891/2f46740c-78f8-6079-ae8a-5c304f377881.png)

<h1>DockerコンテナからホストのDBに接続する方法</h1>
最後に、Dockerコンテナから、ホストのローカルSQL Serverに接続する方法です。<br>DBコンテナも一緒に立ち上げる方が簡単で、開発環境を統一しやすいメリットがあるので、あまり使わないかもしれません。

<h3>手順１. TCP/IPを有効にする</h3>
SQL Server構成マネージャーを開いて、TCP/IPを有効にします。<br>そして、TCP/IPのプロパティのIPAllの「TCP 動的ポート」を後で使うので、メモしておきます。

![画像13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1893891/758b9819-33d3-eab9-914e-d4a97dab688f.png)

<h3>手順２. SQL Serverの再起動</h3>

SQL Serverのサービスから、SQL Serverの再起動をします。
![画像14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1893891/60b224b2-d31a-261a-89c7-9c2f587b7a8f.png)

<h3>手順３. 接続先を変更</h3>
appsettings.jsonのDB接続先を変更します。

```json:appsettings.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "RazorPageSampleContext": "Server=host.docker.internal,50489; Database=razor_sample_db; User ID=sa; MultipleActiveResultSets=true;App=EntityFramework;"
  }
}
```

localhost,1401;だった部分を、<strong>host.docker.internal,50489;</strong>に変更しています。<br>「host.docker.internal」はコンテナから、ホストのネットワークにアクセスするためのDNS名になります。ポート番号は、[手順１. TCP/IPを有効にする](#手順１-tcpipを有効にする)で、メモしたポート番号を指定します。<br>あとは、ユーザーはsaのままで、パスワードは設定していないので、IDだけの指定になっています。

以上の設定で、ホストのローカルDBに繋がるようになるはずです。