Freman.Sample.Web Blazor + PostgreSQL (Docker) sample

This repo is a small demo for C# developers who are new to web development. The goal is a “clone → run → it works” experience, while also showing common real-world patterns:

  • PostgreSQL in Docker
  • EF Core migrations
  • A Blazor WebAssembly UI that talks to the server via HTTP APIs
  • A confirmation modal + optimistic UI updates

Whats in this solution?

Projects

  • Freman.Sample.Web (Server / Host)

    • ASP.NET Core app
    • Owns PostgreSQL + EF Core (DbContext, entities, migrations)
    • Exposes HTTP endpoints under /api/* (Minimal APIs)
    • Hosts the Blazor app and serves the WASM client
  • Freman.Sample.Web.Client (Client / Browser)

    • Blazor WebAssembly UI
    • Calls the server API with HttpClient
    • Contains the demo UI page at /notes
    • Includes a reusable confirmation modal component (ConfirmModal.razor)
  • Freman.Sample.Web.Contracts (Shared contracts)

    • Shared request/response models (“DTOs”) used by both server and client
    • Keeps the API “shape” consistent and avoids duplicating types

Rule of thumb: DB access and secrets stay on the server. The client only calls HTTP APIs.

Demo features

  • /notes page:
    • Create a note (validation + loading/saving UI)
    • List notes (loaded from /api/notes)
    • Delete a note (with confirmation modal)
    • Uses “optimistic UI” for delete (removes from list immediately, rolls back if the API fails)

Shared Contracts (why Freman.Sample.Web.Contracts exists)

In “real” web apps, the server and client often need to agree on the JSON payloads they send each other. If you define those types twice (once in Server and once in Client), they will eventually drift.

This sample uses Freman.Sample.Web.Contracts to share:

  • NoteDto (what the API returns)
  • CreateNoteRequest (what the API accepts when creating a note)
  • Validation attributes (Data Annotations) that work on both sides

Why EF entities are NOT shared

EF Core entities (and DbContext) stay in the Server project because:

  • They represent persistence concerns (tables/relationships), not API contracts
  • They can include server-only behavior and configuration
  • You generally dont want browsers depending on your database schema

So the flow is:

  • Entity (Server-only) ↔ mapped to ↔ DTO (Contracts) ↔ sent to ↔ Client

Prereqs

  • Docker Desktop
  • .NET SDK (matching the repo)
  • (Optional) EF tooling: dotnet-ef

  1. Create a .env file next to compose.yaml:
POSTGRES_PASSWORD=your_local_dev_password
  1. Start everything:
powershell docker compose up --build
  1. Open the app and navigate to /notes. Create a note, refresh the page — it should persist.

Running from Rider (server on host, DB in Docker)

Start just PostgreSQL:

powershell docker compose up postgres

Then run the Freman.Sample.Web project from VS/Rider.

In this mode, the server uses the connection string in Freman.Sample.Web/appsettings.Development.json (which points to localhost:5432).


Database + EF Core migrations

About migrations in this repo

This sample includes EF Core migrations and (for developer convenience) the server applies migrations automatically on startup in Development.

EF CLI setup

We use a local tool manifest (repo-friendly):

powershell dotnet tool restore

If you prefer global installation instead, you can install/update dotnet-ef globally.

Add a migration

Run from the solution root: powershell dotnet tool run dotnet-ef -- migrations add InitialCreate --project .\Freman.Sample.Web\Freman.Sample.Web\Freman.Sample.Web.csproj --startup-project .\Freman.Sample.Web\Freman.Sample.Web\Freman.Sample.Web.csproj ` --output-dir Data\Migrations

Apply migrations manually (optional)

Normally the server will apply migrations automatically in Development, but you can also run:

powershell dotnet tool run dotnet-ef -- database update --project .\Freman.Sample.Web\Freman.Sample.Web\Freman.Sample.Web.csproj --startup-project .\Freman.Sample.Web\Freman.Sample.Web\Freman.Sample.Web.csproj

API overview

  • GET /api/notes → list recent notes
  • POST /api/notes → create note
  • DELETE /api/notes/{id} → delete note

The client calls these endpoints using HttpClient and JSON.


Validation notes

The /notes form uses Data Annotations ([Required], [StringLength]) because theyre simple and familiar.

If you outgrow Data Annotations, a common next step is FluentValidation:

  • more expressive rules
  • better composition
  • easier conditional validation and custom rule sets

Render mode / hosting notes (important!)

This solution uses a mixed Blazor setup. For pages meant to run in the browser (WASM) and use the client DI container (including the WASM HttpClient), the page uses:

  • @rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false))

Why prerender: false?

  • Without it, the component may be instantiated on the server during prerender, where HttpClient is not registered the same way and injection can fail.

Troubleshooting

  • DB auth failed

    • Ensure .env password matches the value used by Compose.
    • If you changed the password after the volume was created, you may need to reset the volume:
      • stop containers
      • remove the named volume
      • start again
  • Port 5432 already in use

    • Stop any other local PostgreSQL instance or change the port mapping in compose.yaml.
  • Migrations/EF errors like “Unable to retrieve project metadata”

    • Make sure the terminal is using the correct .NET SDK for this repo.
    • Ensure the dotnet-ef tool version matches the EF Core version used by the project.
    • Run dotnet tool restore from the repo root.
Description
No description provided
Readme 767 KiB
Languages
HTML 35.8%
C# 31.1%
CSS 24.5%
JavaScript 4.8%
Dockerfile 3.8%