172 lines
5.8 KiB
Markdown
172 lines
5.8 KiB
Markdown
# 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
|
||
|
||
---
|
||
|
||
## What’s 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 don’t 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`
|
||
|
||
---
|
||
|
||
## Quickstart (recommended): run everything with Docker Compose
|
||
|
||
1) Create a `.env` file next to `compose.yaml`:
|
||
```
|
||
POSTGRES_PASSWORD=your_local_dev_password
|
||
```
|
||
|
||
2) Start everything:
|
||
```
|
||
powershell docker compose up --build
|
||
```
|
||
|
||
3) 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 they’re 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.
|