ASP.NET Booklog — 読書ログ(ASP.NET Core + EF Core + PostgreSQL + Docker + Render)

App:https://aspnet-booklog.onrender.com/ ※1
GitHub:https://github.com/ynakao55/aspnet-booklog
※1 サービスが停止している場合は、起動に50秒程かかります。

「本のタイトル・著者・メモ・ステータス(未読/読書中/読了)」を管理する読書ログ Web アプリを、ASP.NET Core + EF Core + PostgreSQLで作り、Dockerで動かし、Render.comにデプロイしました。
この記事はポートフォリオ向けに、プロジェクト構成・主要ファイルの役割と作成ポイント・実行/デプロイ手順・ハマりどころをまとめたものです。


目次

1. アプリの説明

1-1. 何をするアプリ?

  • 書籍(Title/Author/Note)を登録・一覧表示
  • ステータス(unread/reading/done)でフィルタ
  • 一覧は新しい順に表示、Note は長文でもトリムして見やすく(例:先頭 30 文字 + 省略記号)

1-2. 主な機能

  • CRUD(今回は Create + Read の最小構成)
  • フィルタ(クエリ文字列 or 画面リンク)
  • 画面レイアウト(ヘッダー/フッターをレイアウトファイルで共通化)
  • 初回起動時に自動マイグレーションでテーブル作成

1-3. 技術スタック

  • フレームワーク:ASP.NET Core 8(Razor Pages)
  • ORM:Entity Framework Core + Npgsql Provider
  • DB:PostgreSQL(Render のマネージド DB)
  • インフラ:Docker / Render.com
  • 言語:C#
  • OS/実行:Linux コンテナ

2. 学んだ技術とハマりどころ

  • EF Core のマイグレーション
    Docker 内で dotnet-ef をローカルツールとして実行し、src/Migrations/Git にコミットしておくのが本番デプロイを安定させるコツ。
  • DATABASE_URL の取り扱い(Render)
    Render の Postgres は postgres:// 形式。C# で NpgsqlConnectionStringBuilder を使って SslMode=Require などを付与して接続文字列を再構成すると安全。環境変数が未設定だと localhost:5432 に落ちるのでログで早期検知できるようにした。
  • 自動マイグレーション
    Program.cs 起動時に db.Database.Migrate() を呼ぶことで、無料プラン(Pre-Deploy コマンドなし)でも初回にテーブルが作成されるようにした。
  • Docker マルチステージ
    SDK イメージでビルド → ランタイムイメージに成果物だけコピー。軽量で速い。
  • Razor Pages の構造化
    _Layout.cshtml にヘッダー/フッター/メニューをまとめ、Index は一覧とフィルタ UI に集中。

3. プロジェクト構成と主要ファイル(抜粋)

aspnet-booklog/
├─ src/
│  ├─ aspnet-booklog.csproj
│  ├─ Program.cs                # 起動、DI、DB接続、Auto Migrate
│  ├─ Data/
│  │  └─ AppDbContext.cs        # EF Core DbContext(Books)
│  ├─ Models/
│  │  └─ Book.cs                # エンティティ(Id/Title/Author/Status/Note)
│  ├─ Pages/
│  │  ├─ Index.cshtml.cs        # 一覧/フィルタのハンドラ
│  │  ├─ Index.cshtml           # 一覧画面(テーブル + フィルタ UI)
│  │  └─ Shared/
│  │     └─ _Layout.cshtml      # 共通レイアウト
│  └─ Migrations/               # 生成されたマイグレーション(Git 管理)
├─ Dockerfile
└─ .dockerignore

3-1) Program.cs

役割:サービス登録、DATABASE_URL の再構成、DbContext の DI、起動時に Migrate()

作成ポイント

  • Environment.GetEnvironmentVariable("DATABASE_URL") を読み、postgres://postgresql:// に置換。
  • NpgsqlConnectionStringBuilderSslMode=RequireTrustServerCertificate=true(Render では実務上これで可)。
  • builder.Services.AddDbContext<AppDbContext>(opt => opt.UseNpgsql(connString));
  • 起動直後に using var scope = app.Services.CreateScope(); db.Database.Migrate();

3-2) Data/AppDbContext.cs

役割:EF Core のコンテキスト。
作成ポイント

  • EF Core を使って Book エンティティをデータベースにマッピング
  • アプリケーションからデータベース操作を行うための設定を行う。

3-3) Models/Book.cs

役割:エンティティ。
作成ポイント

  • バリデーション属性([Required][MaxLength])を付ける。
        [Required, MaxLength(120)]
        public string Title { get; set; } = "";

3-4) Pages/Index.cshtml.cs

役割:一覧表示&フィルタのハンドラ(OnGet)。
作成ポイント

  • クエリ status を受け取って LINQ で絞り込み。
  • 新しい順に並べる:OrderByDescending(b => b.Id)
  • ページモデルに BooksCurrentStatus を用意。

擬似コード例:

public class IndexModel : PageModel
{
    private readonly AppDbContext _db;
    public IReadOnlyList<Book> Books { get; private set; } = [];
    public BookStatus? CurrentStatus { get; private set; }

    public IndexModel(AppDbContext db) => _db = db;

    public async Task OnGetAsync(string? status)
    {
        IQueryable<Book> q = _db.Books.AsNoTracking();
        if (Enum.TryParse<BookStatus>(status, true, out var st))
        {
            CurrentStatus = st;
            q = q.Where(b => b.Status == st);
        }
        Books = await q.OrderByDescending(b => b.Id).ToListAsync();
    }
}

3-5) Pages/Index.cshtml

役割:一覧テーブル&フィルタ UI。
作成ポイント

  • 先頭に「[New Book]」ボタン、右側にフィルタ(All / Unread / Reading / Done)。
  • Note はトリムして表示
  • 0 件時もフィルタは表示(UX が安定)。

UI のポイント(抜粋):

<div class="toolbar">
  <a class="btn" href="/Create">New Book</a>
  <span class="filters">
    Filter:
    <a href="/">All</a> |
    <a href="/?status=Unread">Unread</a> |
    <a href="/?status=Reading">Reading</a> |
    <a href="/?status=Done">Done</a>
  </span>
</div>

<table class="table">
  <thead>
    <tr>
      <th>Title</th><th>Author</th><th>Status</th><th>Note</th><th></th>
    </tr>
  </thead>
  <tbody>
    @foreach (var b in Model.Books)
    {
      <tr>
        <td>@b.Title</td>
        <td>@b.Author</td>
        <td>@b.Status</td>
        <td>@(string.IsNullOrEmpty(b.Note) ? "" : (b.Note.Length <= 30 ? b.Note : b.Note.Substring(0,30) + "…"))</td>
        <td><a href="/Details?id=@b.Id">Show</a></td>
      </tr>
    }
  </tbody>
</table>

3-6) Pages/Shared/_Layout.cshtml

役割:サイト共通の枠(ヘッダー/フッター/メニュー/スタイル)。
作成ポイント

  • ヘッダーにサイトタイトル「WEBアプリ開発教室」等を置き、メニューに HomeNew Book を設置。
  • 本文は @RenderBody()、必要なら @RenderSection("Scripts", required: false)

3-7) Dockerfile

役割:マルチステージでビルドして軽量ランタイムにデプロイ。
作成ポイント

  • ASPNETCORE_URLS=http://0.0.0.0:8080 を環境変数で指定(Render/ローカルで同じ)。
  • UseAppHost=false でアルパイン系でも動作安定。

3-8) .dockerignore

役割:不要ファイルをビルドコンテキストから除外し、転送・ビルドを高速化。

**/bin/
**/obj/
**/.vs/
**/*.user
**/*.suo
.git
.gitignore
Dockerfile

4. ローカル実行 & Render デプロイ(チェックリスト)

4-1. マイグレーションの作成(Docker 内で)

cd aspnet-booklog

# 必要パッケージ
docker run --rm -v "$PWD/src:/src" -w /src mcr.microsoft.com/dotnet/sdk:8.0 \
  bash -lc 'dotnet add package Microsoft.EntityFrameworkCore.Design'

# ツール → 生成
docker run --rm -v "$PWD/src:/src" -w /src mcr.microsoft.com/dotnet/sdk:8.0 bash -lc '
  set -e
  test -f /src/.config/dotnet-tools.json || dotnet new tool-manifest
  dotnet tool install dotnet-ef || true
  dotnet tool restore
  dotnet ef migrations add InitialCreate --output-dir Migrations
'

# → src/Migrations/ を Git にコミット!

4-2. ローカル実行

docker build -t aspnet-booklog .

docker run --rm -p 8080:8080 \
  -e DATABASE_URL="postgresql://<user>:<pass>@<host>:<port>/<db>" \
  aspnet-booklog

# http://localhost:8080

4-3. Render デプロイ

  • Web Service を Docker リポジトリから作成(Dockerfile 自動検出)。
  • EnvironmentDATABASE_URL を追加(Postgres の External Database URL)。
  • Start Command は Dockerfile の ENTRYPOINT に任せる(未設定でOK)。
  • 初回アクセス時に自動マイグレーションでテーブル作成。

トラブル対処

  • ログに localhost:5432 が見えたら → DATABASE_URL 未設定/綴りミス
  • No migrations were foundsrc/Migrations/ をコミットし忘れ

5. スクリーンショット


6. まとめ

  • ASP.NET Core + EF Core + PostgreSQL を Docker で包むと、ローカルも本番(Render)も同じアーティファクトで再現性高く運用できます。
  • DATABASE_URL を環境変数で一元化し、起動時に Migrate() を呼ぶことで Render 無料プランでもスムーズに初期化できます。
  • Razor Pages はレイアウトとページモデルを分けると、UI とロジックの見通しが良く、ポートフォリオとしても読みやすい構成になります。

付録:依存パッケージ(NuGet)

  • Npgsql.EntityFrameworkCore.PostgreSQL
  • Npgsql
  • Microsoft.EntityFrameworkCore.Design

必要なら dotnet add package を Docker 経由で実行しておくと、ローカルに .NET SDK を入れなくても開発できます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

目次