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

GitHub:https://github.com/ynakao55/aspnet-booklog

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


目次

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
  • インフラ:Docker
  • 言語:C#
  • OS/実行:Linux コンテナ

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

  • EF Core のマイグレーション
    Docker 内で dotnet-ef をローカルツールとして実行し、src/Migrations/Git にコミットしておくのが本番デプロイを安定させるコツ。
  • 自動マイグレーション
    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
  • 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 を設置。

3-7) Dockerfile

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

  • ASPNETCORE_URLS=http://0.0.0.0:8080 を環境変数で指定。
  • UseAppHost=false でアルパイン系でも動作安定。

3-8) .dockerignore

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

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

4.aspnet-booklog の実行方法(ローカル / Web)

ここからは、URL短縮アプリ aspnet-booklog の実行方法をまとめます。
ローカル環境(WSL + Docker)で動かす方法と、公開リンクから試す方法の両方を記載しています。

ローカルで実行する方法(Docker / WSL)

前提

  • WSL(Windows Subsystem for Linux)がインストール済み
  • Docker Desktop がインストール済み
  • WSL と Docker の連携設定(WSL integration)が有効

1. Git をインストール(未インストールの場合)

sudo apt update
sudo apt install -y git

2. リポジトリをクローン

git clone https://github.com/ynakao55/aspnet-booklog.git
cd aspnet-booklog

3. Docker Compose で起動

通常の起動 / 停止

docker compose up -d --build
docker compose down

確実に作り直して起動したい場合(推奨)
コンテナ・ボリューム・不要な関連コンテナも整理してから再作成します。

docker compose down -v --remove-orphans
docker compose up -d --build --force-recreate

4. ブラウザで開く

起動後、ブラウザで以下のURLを開きます。

  • http://localhost:8080

公開リンクから実行する方法(Web)

以下の公開URLからアクセスできます。


ローカル実行時補足(トラブルシュート)

コンテナの状態を確認

docker compose ps

ログを確認

docker compose logs -f

画面が開かない / 反映されない場合

docker compose down -v --remove-orphans
docker compose up -d --build --force-recreate

5. スクリーンショット


6. まとめ

  • ASP.NET Core + EF Core + PostgreSQL を Docker で包むと、ローカルも本番も同じアーティファクトで再現性高く運用できます。
  • Razor Pages はレイアウトとページモデルを分けると、UI とロジックの見通しが良く、ポートフォリオとしても読みやすい構成になりました。

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

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

GitHub:https://github.com/ynakao55/aspnet-booklog

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

この記事を書いた人

コメント

コメントする

目次