A small interactive console application that lets you build, edit, persist and replay decks of playing- or divination-cards. The project started as a sandbox for OOP and grew into a portfolio piece showing how the same domain can be expressed cleanly with DI, Command pattern, Memento and source-generated APIs.
- Generic Host + DI —
Host.CreateApplicationBuilderwith everything resolved through the container; noSingletonpatterns anywhere. IHostedServicedrives the console loop with cooperative shutdown on Ctrl-C.- Declarative menu DSL —
MenuBuilder.Root(...)fluently composes the menu tree on top of the existingIMenu/IMenuItemframework. - Command pattern — every destructive operation (
AddCardCommand,RemoveCardCommand,SortByPowerCommand, …) implementsIDeckCommandand goes through aCommandRunnerthat snapshots a memento before applying. - Real Undo/Redo via Memento —
DeckHistory(two-stack) lets users walk forward and back through their edits; failed commands roll their memento back automatically. - System.Text.Json source-generated persistence — no reflection at runtime,
AOT-friendly serialisation via
[JsonSerializable]+JsonSerializerContext. [LoggerMessage]source generator for allocation-free structured logs.- Sealed records (
Card,CardsMemento,DeckSnapshot) for invariant-preserving immutable data. - xUnit + FluentAssertions test suite (21 tests covering deck invariants, history, command runner, manager and menu builder).
CardsTools/
├── Program.cs Host.CreateApplicationBuilder composition root
├── Hosting/
│ └── ConsoleAppService.cs BackgroundService driving the console UI
├── Menu/
│ └── MenuBuilder.cs Fluent DSL on top of the menu framework
├── Commands/
│ ├── IDeckCommand.cs Command pattern interface + base class
│ ├── DeckCommands.cs AddCard / RemoveCard / Clear / Sort / Shuffle
│ └── CommandRunner.cs Saves memento → applies command → rolls back on failure
├── Persistence/
│ ├── IDeckStorage.cs Save / Load / ListSaved abstraction
│ ├── JsonDeckStorage.cs Filesystem impl + timestamped backups
│ └── JsonContext.cs System.Text.Json source-generated context
├── Data/
│ ├── Models/
│ │ ├── Card.cs sealed record Card(Id, Name, Description, Power)
│ │ └── DeckOfCards.cs Domain model + OperationResult struct
│ ├── Managers/
│ │ ├── CardManager.cs DI-managed; owns decks + per-deck histories
│ │ └── MenuManager/ Hand-rolled console menu framework
│ │ ├── IMenu.cs, IMenuItem.cs, Menu.cs, MenuItem.cs
│ │ ├── Binding.cs Keyboard → MenuPoint switch expression
│ │ └── MenuPoint.cs, ArgumentActionExecutionEvent.cs
│ └── Tools/
│ ├── CardsMemento.cs Immutable snapshot record
│ ├── CardCollectionHistory.cs DeckHistory: two-stack Undo/Redo
│ └── ValidationCard.cs Tiny input helpers
└── tests/
└── CardsTools.Tests/ xUnit + FluentAssertions
dotnet restore
dotnet runUse ↑/↓ to navigate, Enter to select, Esc to go back, Ctrl+C to quit.
Decks are persisted under DeckCardSave/ with timestamped backups under
DeckCardSave/Backup/<deck>/.
dotnet format --verify-no-changes # check style
dotnet build --configuration Release # build
dotnet test # 21 xUnit tests| Layer | Library |
|---|---|
| Runtime | .NET 10 |
| Hosting / DI | Microsoft.Extensions.Hosting |
| Logging | Serilog + [LoggerMessage] generators |
| JSON | System.Text.Json (source-generated context, AOT-friendly) |
| Tests | xUnit + FluentAssertions |
MIT.