🦈

【C#】SQL Serverと.NETのAPIをDockerで動かす方法

2024/06/09に公開

はじめに

.NETのAPIと、そのデータベースを、すべてDockerで動かす方法を載せておきます。
公式だと、.NETのアプリをDockerでやる方法はチュートリアルで載っていましたが、APIやSQL Serverは見当たりませんでした。
そのため、ハンズオン形式で、まとめておきます。

準備

WSLのインストール:公式サイト
VSCodeのインストール:公式サイト,C#と.NETの環境構築方法 -WindowsとWSL-【VSCode】
Dockerのインストール:WSL 2 での Docker リモート コンテナーの概要 - Microsoft Learn
Dotnetのインストール:C#と.NETの環境構築方法 -WindowsとWSL-【VSCode】

筆者の環境

  • WSL
  NAME                   STATE           VERSION
* Ubuntu                 Running         2

(Powershellでwsl -l -vで確認)

  • Dockerのバージョン
Docker version 24.0.7, build afdd53b

docker --versionで確認)

  • dotnet
8.0.105

(WSLでdotnet --versionで確認)

API(Sample)をDockerで動かす

SQL ServerとAPIを同時に実行する前に、まずAPIのみでDockerで実行できるかやってみましょう。

APIプロジェクトを作る

  1. 以下を実行する
    dotnet-api-sampleディレクトリを作り、ディレクトリに移動します。
mkdir dotnet-api-sample
 cd dotnet-api-sample
  1. dotnet newコマンドでWeb APIを作成
dotnet new webapi
dotnet newコマンドについて
  1. VSCodeを開く
code .
  1. 以下でAPIを実行確認
dotnet run
dotnet runコマンドについて

dotnet run -- 明示的なコンパイルや起動コマンドなしで、ソースコードを実行します。

dotnet runを参照

http://localhost:XXXX/weatherforecastで実行確認できます。
XXXXはポートを入力してください。

Dockerfileを自動作成

  1. Ctrl + Shift + P
  2. Docker: Add Docker Files to Workspace... を選択
  3. 項目を選択していき、Dockerfileを作成

Dockerイメージをビルド

docker build -t <任意のイメージ名> -f Dockerfile .
記述例の説明

仕組みと使い方がわかる Docker&Kubernetesのきほんのきほん のp180ページより引用↓

docker build -t 作成するイメージ名 材料フォルダのパス

以下で、Dockerのイメージ一覧を確認出来ます。

docker images

コンテナを実行

  1. 以下を実行する
docker run --rm -p 5000:<リッスンするポート> <任意のイメージ名>
docker runとは

仕組みと使い方がわかる Docker&Kubernetesのきほんのきほん P81より引用

Dockerイメージをダウンロードし、コンテナを作成して起動する(ダウンロードは必要な場合のみ)。
docker image pull,docker container create,docker containaer startの一連の動作をひとまとめにしたもの

リッスンするポートは、Dockerfile内の以下のXXXXの部分です。

EXPOSE XXXX

のXXXXの部分がリッスンするポートになっているはずです。

EXPOSEとは

仕組みと使い方がわかる Docker&Kubernetesのきほんのきほん のp181ページより引用↓

  • EXPOSE:通信を想定するポートをイメージの利用者に伝える
  1. 動作確認する
    http://localhost:5000/weatherforecastにアクセスしてみます。
    dotnet runのときと同じように見えていればOKです。

  2. 動作の停止
    ctrl + cを押せば、動作が停止し、コンテナが削除されます。

参考サイト

https://qiita.com/mkin/items/ece1641ca2cbf366d217

API、SQL ServerをDockerで動かす

APIがDockerで動かせることを確認したところで、実際にAPIとSQL ServerをDockerで動かす手順をやっていきます。
完成したAPIは、以下のGitHubにて確認できます。

https://github.com/Haru-330/.NET_API_Docker_Sample/tree/main

プロジェクト作成

  1. 以下を実行する
    dotnet-api-sampleディレクトリを作り、ディレクトリに移動します。
mkdir dotnet-api-sample
 cd dotnet-api-sample
  1. dotnet newコマンドでWeb APIを作成
dotnet new webapi
  1. VSCodeを開く
code .
  1. 以下でAPIを実行確認
dotnet run
ファイル構成を確認

📁 dotnet-api-sample
└──📂 bin
└──📂 obj
└──📂 Properties
├── appsettings.Development.json
├── appsettings.json
├── dotnet-api-sample.csproj
├── dotnet-api-sample.http
├── dotnet-api-sample.sln
└── Program.cs

gitで管理したい場合の手順
  1. 以下のコマンドでgitignoreを作成
dotnet new gitignore
  1. 以下のコマンドでgitを使えるように。
git init
  1. 以下でブランチをMasterからmainに変更
git branch -m main

ここで、git add --allgit commit -m "Initial Commit"しておくと良いと思います。

クラスを作成

  1. Entitiesフォルダーを作り、Bookクラスを作成
    参考サイトとは違い、namespaceで囲わない書き方にしています。
    また、Warningが出るため、requiredを追加しています。
namespace SimpleWebApi.Entities;
public class Book
{
    public int BookId { get; set; }
    public required string Title { get; set; }
    public required string Author { get; set; }
}
  1. Dataフォルダーを作り、BookContextクラスを作成
    参考サイトとは違い、namespaceで囲わない書き方にしています。
using Microsoft.EntityFrameworkCore;
using SimpleWebApi.Entities;

namespace SimpleWebApi.Data;
public class BookContext : DbContext
{
    public BookContext(DbContextOptions options) : base(options)
    {
    }

    public DbSet<Book> Books { get; set; }
}
  1. Controllersフォルダーを作り、BooksControllerクラスを作成
    参考サイトとは違い、namespaceで囲わない書き方にしています。
using Microsoft.AspNetCore.Mvc;
using SimpleWebApi.Data;
using SimpleWebApi.Entities;

namespace SimpleWebApi.Controllers;
[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{
    private readonly BookContext context;

    public BooksController(BookContext context)
    {
        this.context = context;
    }

    [HttpGet]
    public ActionResult<IEnumerable<Book>> List()
    {
        var books = context.Books;
        return books;
    }
}
ファイル構成を確認

📁 dotnet-api-sample
├──📂 bin
├──📂 obj
├──📂 Properties
├──📂 Controllers
│ └── BooksControllers.cs
├──📂 Data
│ └── BookContext.cs
├──📂 Entities
│ └── Book.cs
├── appsettings.Development.json
├── appsettings.json
├── dotnet-api-sample.csproj
├── dotnet-api-sample.http
├── dotnet-api-sample.sln
└── Program.cs

必要なパッケージをインストール

  1. 以下のコマンドを実行して、EntityFrameworkCoreをインストール
dotnet add package Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCoreとは

Entity Framework Core(EFコア)は、SQL Server(オンプレミスおよびAzure)、SQLite、MySQL、PostgreSQL、Oracle、Azure Cosmos DBなど、さまざまなデータベースに対して、クリーンでポータブルな高レベルのデータアクセスレイヤーを.NET(C#)で構築できる最新のオブジェクトデータベースマッパーです。
LINQクエリ、変更追跡、更新、スキーマ移行をサポートします。(原文日本語翻訳)

引用:https://www.nuget.org/packages/Microsoft.EntityFrameworkCore

  1. 以下でEntityFrameworkCore.Designをインストール
dotnet add package Microsoft.EntityFrameworkCore.Design
Microsoft.EntityFrameworkCore.Designとは

Entity Framework Coreツールは、設計時の開発作業を支援します。
主に、マイグレーションを管理したり、データベース.DbContextのスキーマをリバースエンジニアリングすることで、エンティティタイプを足場するために使用されます。
このパッケージは、コマンドラインまたはパッケージマネージャーコンソールベースのツーリングに必要で、dotnet-efとMicrosoft.EntityFrameworkCore.Tools.Microsoft.EntityFrameworkCore.Designの依存関係です。(原文日本語翻訳)

引用:https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Design/

  1. Microsoft.Extensions.Configurationをインストール
dotnet add package Microsoft.Extensions.Configuration
Microsoft.Extensions.Configurationとは

Microsoft.Extensions.Configurationは、コアコンフィギュレーション抽象化と組み合わされており、.Configurationの形式でキー/値ペアのコンフィギュレーション値を取得するために、さまざまな種類のコンフィギュレーションプロバイダを構築することができます。
環境変数、インメモリコレクション、JSON、INI、またはXMLファイルから読み取るための組み込みコンフィギュレーションプロバイダの実装が多数あります。
組み込みのバリエーションとは別に、さまざまな設定サービスやその他のデータソースと統合するために、コミュニティから出荷されたライブラリもあります。Microsoft.Extensions.Configuration.AbstractionsIConfiguration(原文日本語翻訳)

引用:https://www.nuget.org/packages/Microsoft.Extensions.Configuration/

  1. Swashbuckle.AspNetCore -v 6.6.2をインストール
dotnet add package Swashbuckle.AspNetCore -v 6.6.2
Swashbuckle.AspNetCoreとは

ASP.NET Coreで構築されたAPIを文書化するためのSwaggerツール

引用:https://www.nuget.org/packages/Swashbuckle.AspNetCore

  1. 以下でSqlServerをダウンロード
 dotnet add package Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.SqlServerとは

Microsoft.EntityFrameworkCore.SqlServerは、Microsoft SQL ServerおよびAzure SQL用のEF Coreデータベースプロバイダパッケージです。

引用:https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.SqlServer/

`dotnet add package`コマンド

dotnet add package - プロジェクト ファイルにパッケージ参照を追加または更新します。

dotnet add packageを参照

既存のファイルの書き換え

  1. Program.csを書き換える

builder.Services.AddSwaggerGen();の下に書く

var configuration = builder.Configuration; // Use the Configuration property of the builder object

builder.Services.AddDbContext<BookContext>(options => options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddEndpointsApiExplorer();を削除し、以下を追記する

builder.Services.AddControllers();

app.UseHttpsRedirection();の下に追記する

app.MapControllers();

一番上に、以下を追記する

using Microsoft.EntityFrameworkCore;
using SimpleWebApi.Data;

var app = builder.Build();の下に追記

// Apply pending migrations
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    try
    {
        var context = services.GetRequiredService<BookContext>();
        context.Database.Migrate();
    }
    catch (Exception ex)
    {
        var logger = services.GetRequiredService<ILogger<Program>>();
        logger.LogError(ex, "An error occurred while migrating the database.");
    }
}

app.MapControllers();の上に追記

app.UseRouting();
追記したコードの説明
  • builder.Configuration;
    configurationを利用するために指定しています。

  • builder.Services.AddDbContext<BookContext>(options => options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));

    • AddDbContextとは?

      指定されたコンテキストを IServiceCollection にサービスとして登録します。(原文日本語翻訳)

    • UseSqlServerとは?

      Microsoft SQL Server データベースに接続するようにコンテキストを設定しますが、 DbConnection や接続文字列の初期設定は行いません。(原文日本語訳)

    • GetConnectionStringとは?

      GetConnectionString() メソッドは、キーがConnectionStrings:<connection string name>である構成値を探していることに注意してください。

引用:https://learn.microsoft.com/ja-jp/ef/core/miscellaneous/connection-strings
引用:https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.sqlserverdbcontextoptionsextensions.usesqlserver?view=efcore-8.0
引用:https://learn.microsoft.com/ja-jp/dotnet/api/microsoft.extensions.dependencyinjection.entityframeworkservicecollectionextensions.adddbcontext?view=efcore-8.0

  • builder.Services.AddControllers();

指定した IServiceCollection にコントローラ用のサービスを追加します。
このメソッドは、ビューやページで使用するサービスは登録しません。(言語日本語翻訳)

引用:https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.mvcservicecollectionextensions.addcontrollers?view=aspnetcore-8.0

  • 以下のusing
using Microsoft.EntityFrameworkCore;
using SimpleWebApi.Data;

using ディレクティブを使用すると、その型の完全修飾名前空間を指定せずに、名前空間で定義された型を使用できます。

引用:https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/using-directive

  • app.MapControllers();

REST API では、属性ルーティングを使用して、HTTP 動詞で操作を表現するリソースのセットとしてアプリの機能をモデル化する必要があります。
属性ルーティングでは、属性のセットを使ってアクションをルート テンプレートに直接マップします。 次のコードは、REST API では一般的であり、次のサンプルで使用されます。

引用:https://learn.microsoft.com/ja-jp/aspnet/core/mvc/controllers/routing?view=aspnetcore-8.0

  • 以下のコードの説明
// Apply pending migrations
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    try
    {
        var context = services.GetRequiredService<BookContext>();
        context.Database.Migrate();
    }
    catch (Exception ex)
    {
        var logger = services.GetRequiredService<ILogger<Program>>();
        logger.LogError(ex, "An error occurred while migrating the database.");
    }
}

このコードは、ASP.NET Coreの依存性注入(DI)システムを使用して、データベースのマイグレーションを行うためのものです。

  • app.Services.CreateScope()は、新しいスコープを作成します。スコープは、依存性注入コンテナからサービスを解決するための一時的な境界を提供します。
  • var services = scope.ServiceProvider;で、スコープからサービスプロバイダを取得します。サービスプロバイダは、登録されたサービスを解決するためのメカニズムを提供します。
  • var context = services.GetRequiredService<BookContext>();で、BookContextという名前のサービスを解決します。これは、Entity Framework CoreのDbContextの一部で、データベースとの対話を管理します。
  • context.Database.Migrate();は、データベースのマイグレーションを実行します。これにより、データベーススキーマが最新の状態に更新されます。
  • catch (Exception ex)ブロックでは、マイグレーション中にエラーが発生した場合に、そのエラーをログに記録します。

(Copilotで生成)

  • app.UseRouting();

UseRouting では、ルートの照合がミドルウェア パイプラインに追加されます。
このミドルウェアによって、アプリで定義されているエンドポイントのセットが調べられ、要求に基づいて最適な一致が選択されます。

引用:https://learn.microsoft.com/ja-jp/aspnet/core/fundamentals/routing?view=aspnetcore-8.0

以下は削除

record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/weatherforecast", () =>
{
    var forecast =  Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();
Program.csの全コード
using Microsoft.EntityFrameworkCore;
using SimpleWebApi.Data;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddControllers();
builder.Services.AddSwaggerGen();

var configuration = builder.Configuration; // Use the Configuration property of the builder object

builder.Services.AddDbContext<BookContext>(options => options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));

var app = builder.Build();

// Apply pending migrations
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    try
    {
        var context = services.GetRequiredService<BookContext>();
        context.Database.Migrate();
    }
    catch (Exception ex)
    {
        var logger = services.GetRequiredService<ILogger<Program>>();
        logger.LogError(ex, "An error occurred while migrating the database.");
    }
}

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseRouting(); // 追加

app.MapControllers(); //追加

app.Run();
  1. appsettings.jsonを書き換える

<PASSWORD>には、適切なパスワードを、<DATABASE_NAME>には、任意のデータベース名を入れてください。

  "ConnectionStrings": {
    "DefaultConnection": "Server=sql;Database=<DATABASE_NAME>;User Id=sa;Password=<PASSWORD>;MultipleActiveResultSets=true;TrustServerCertificate=True;"
  },
ConnectionStringsとは?

接続文字列を指定します。

  • Server=xxx
    Serverの名前を指定します。
  • Database=<DATABASE_NAME>
    接続するデータベースの名前を指定します。
  • User Id=sa
    データベースへの接続に使用するユーザーIDを指定します。
  • Password=<PASSWORD>
    ユーザーIDに関連付けられたパスワードを指定します。
  • MultipleActiveResultSets=true
    複数のアクティブな結果セットの有効化をする

引用:https://learn.microsoft.com/ja-jp/dotnet/framework/data/adonet/sql/enabling-multiple-active-result-sets

  • TrustServerCertificate=True
    TrustServerCertificate キーワードは、有効な証明書を使用して SQL Server インスタンスに接続する場合にのみ使用できます。
    TrustServerCertificate を true に設定した場合、トランスポート層に SSL が使用されてチャネルが暗号化されます。また、証明書チェーンをたどることによる信頼性の検証は省略されます。

引用:https://learn.microsoft.com/ja-jp/dotnet/framework/data/adonet/connection-string-syntax

  • DefaultConnection
    GetConnectionString()で指定する名前です。
    今回はDefaultConnectionとしています。

Dockerでデータベースを作り、接続する

  1. Dockerfileを作成する

XXXXはポートを入力してください。

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE XXXX

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

FROM build AS publish
RUN dotnet publish "dotnet-api-sample.csproj" -c Release -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENV ASPNETCORE_URLS="http://*:XXXX"
ENTRYPOINT ["dotnet", "dotnet-api-sample.dll"]
Dockerfileの記述内容の読み方

仕組みと使い方がわかる Docker&Kubernetesのきほんのきほん のp180ページより引用↓

  • FROM:元にするイメージを指定する
  • WORKDIR:RUN、CMD、ENTRYPOINT、ADD、COPYの際の作業ディレクトリを指定する
  • COPY:イメージにファイルやフォルダを追加する
  • RUN:イメージをビルドするときにコマンドを実行する
  • ENV:環境変数を定義する
  • ENTRYPOINT:イメージを実行するときのコマンドを強要する

以下は、マルチステージ ビルドを使う - Docker ドキュメントより引用↓

  • AS:ステージに対して名前を付ける
  1. 以下のコマンドをうち、Migrationファイルを作成する
dotnet ef migrations add Initial
dotnet ef migrations add Initialとは

EF Core により、ご自分のプロジェクトに Migrations というディレクトリが作成され、いくつかのファイルが生成されます。

https://learn.microsoft.com/ja-jp/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli

  1. 以下のコマンドをうち、イメージを作成する

<IMAGE_NAME>には、任意のイメージ名を入れてください。

docker build -t <IMAGE_NAME> .
  1. docker-compose.ymlを作る

<IMAGE_NAME>には、先程設定したものを、XXXXはポートを、<PASSWORD>には、適切なパスワードを、<DATABASE_NAME>には、任意のデータベース名を入れてください。

# Please refer https://aka.ms/HTTPSinContainer on how to setup an https developer certificate for your ASP.NET Core service.

version: '3.4'

services:
  simplewebapi:
    image: <IMAGE_NAME>
    build:
      context: .
      dockerfile: Dockerfile
    depends_on:
      - sql
    environment:
      - ConnectionStrings__DefaultConnection=Server=sql;Database=<DATABASE_NAME>;User Id=sa;Password=<PASSWORD>;MultipleActiveResultSets=true;TrustServerCertificate=True;
    ports:
      - XXXX:XXXX
  sql:
    image: "mcr.microsoft.com/mssql/server:2022-latest"
    container_name: sql-server-zenn
    ports: # not actually needed, because the two services are on the same network.
      - "1433:1433" 
    environment:
      - ACCEPT_EULA=y
      - SA_PASSWORD=<PASSWORD>
docker-compose.ymlの記述内容の説明

simplewebapi,sql:コンテナ名
仕組みと使い方がわかる Docker&Kubernetesのきほんのきほん のP213より引用

services:コンテナに関する定義をする
image:利用するイメージを指定する
depends_on:別のサービスに依存することを示す
environment:環境変数を定義する
ports:ポートのマッピングを指定する

Compose ファイル・リファレンスより引用

build:構築時に適用するオプションを指定
container_name:デフォルトで生成される名前の代わりに、カスタム・コンテナ名を指定
※Docker コンテナ名はユニークである必要がある

  1. 2022_latestバージョンをダウンロードする
docker pull mcr.microsoft.com/mssql/server:2022-latest
docker pullとは

仕組みと使い方がわかる Docker&Kubernetesのきほんのきほん のP81より引用

docker pull : Docker Hubなどのリポジトリからイメージをダウンロードする

  1. docker-composeを実行する

ディレクトリがdotnet-api-sampleの状態で、以下を実行します。

docker-compose up -d
upオプション

仕組みと使い方がわかる Docker&Kubernetesのきほんのきほん のp223ページより引用↓

up:コンテナを作成し、起動する

  1. 動作確認する

http://localhost:XXXX/api/booksにアクセスして、正常に動作すればOKです。
データベースに値が入っていないので、以下のように表示されますが、適切です。

もし、値を入れたいという場合は、Azure Data Studioを使って行うのが早いと思います。
参考サイトに掲載されている以下のクエリを実行すれば、値が確認できるようになります。

INSERT INTO Books (Title, Author) VALUES (N'たったひとつの冴えたやりかた',N'ジェイムズ・ティプトリー・ジュニア');
INSERT INTO Books (Title, Author) VALUES (N'アンドロイドは電気羊の夢を見るか?',N'フィリップ・K・ディック');
INSERT INTO Books (Title, Author) VALUES (N'夏への扉',N'ロバート・A. ハインライン');
INSERT INTO Books (Title, Author) VALUES (N'幼年期の終り',N'アーサー C クラーク');
INSERT INTO Books (Title, Author) VALUES (N'われはロボット',N'アイザック・アシモフ');

SQLコマンドについて
INSERT INTO <テーブル名> (カラム1, カラム2, カラム3, ...) VALUES (バリュー1, バリュー2, バリュー3, ...);

(原文日本語訳)

引用:https://www.w3schools.com/sql/sql_insert.asp

参考サイト

https://qiita.com/tamtamyarn/items/5a755d4d5d59592a82cd
https://www.twilio.com/en-us/blog/containerize-your-aspdotnet-core-application-and-sql-server-with-docker
https://stackoverflow.com/questions/75074480/when-i-try-the-run-my-asp-net-core-web-api-from-docker-i-get-err-empty-response

終わりに

いかがでしたでしょうか?
手順に沿っていくだけで、Docker上のSQL Serverに接続し、それを用いたデータベース、APIが動くことが確認できたでしょうか?
一部記述に誤りがあったり、ここはこういう書き方のほうがいいなどありましたら、気軽にコメント頂ければ幸いです。

Discussion