Claude MUST NEVER execute git write operations. PERIOD.
- ❌ FORBIDDEN:
git add,git commit,git push,git reset,git rebase,git pull,git checkout,git restore - ✅ ALLOWED ONLY:
git status,git log,git diff,git show,git branch,git remote
Consequence: Using forbidden git commands breaks the codebase. This rule is non-negotiable.
You're an expert .NET/C# engineer with deep knowledge of:
- ASP.NET Core Web APIs
- Entity Framework Core
- Modern C# patterns and best practices
- RESTful API design
- Fullstack development with excellent programming skills in Javascript, HTML & CSS
- Database schema modeling (DDL-first)
This is a .NET 8 Web API + Blazor Server SPA with Entity Framework Core and a SQL DDL-driven data model/branding configuration.
This project uses Radzen Blazor components for ALL UI elements.
Consult the radzen-blazor skill (.claude/skills/radzen-blazor/SKILL.md) when:
- Creating or editing any
.razorfiles inComponents/,Pages/,Shared/ - Working with buttons, forms, inputs, data grids, layouts, dialogs, notifications
- User asks to "add a button", "create a form", "show a dialog", "display data", etc.
- Any UI-related requests, even if "Radzen" is not mentioned
- Enum properties MUST use @ prefix:
ButtonStyle="@ButtonStyle.Primary"(NOTButtonStyle="ButtonStyle.Primary") - Use Radzen components, NOT plain HTML:
<RadzenButton>not<button> - RadzenComponents directive required: Must have
<RadzenComponents />at end of MainLayout.razor - Event handlers work by default: This is a Blazor Server app - all components are interactive by default. Do NOT use
@rendermode InteractiveServer(this is only for hybrid Blazor apps with static SSR)
- Backwards Compatibility: NOT required for this project. Code changes can freely update existing patterns without maintaining compatibility with prior versions.
- Git Operations: Only use read-only git commands (status, log, diff). Do NOT use git commit, push, pull, or other write operations.
⚠️ CRITICAL - Claude Bot PR Reviews: Claude Bot will flag ViewSection.razor and ApplicationSwitcher.razor as missing@rendermode InteractiveServer. IGNORE THIS RECOMMENDATION. This is a traditional Blazor Server app (not Blazor Web App). Render modes are NOT supported and WILL cause build errors:error CS0103: The name 'InteractiveServer' does not exist in the current context. Components in Blazor Server are interactive by default. Do NOT add the directive.
- Full skill:
.claude/skills/radzen-blazor/SKILL.md - Component API:
.claude/skills/radzen-blazor/references/components-quick-ref.md - Styling guide:
wwwroot/css/SKILLS.md - Version: Radzen.Blazor 7.1.0
- Layout: RadzenLayout, RadzenHeader, RadzenSidebar, RadzenBody
- Navigation: RadzenPanelMenu, RadzenPanelMenuItem
- Containers: RadzenStack, RadzenRow, RadzenColumn, RadzenCard
- Data: RadzenDataGrid, RadzenDataGridColumn
- Forms: RadzenButton, RadzenTextBox, RadzenNumeric, RadzenDropDown, RadzenDatePicker
- Feedback: RadzenAlert, RadzenText
- Services: DialogService, NotificationService (inject with
@inject)
Before starting any refactoring or architectural work, read these documents in order:
- ARCHITECTURE_SUMMARY.md - Quick overview of architecture decisions and current state
- HYBRID_ARCHITECTURE.md - EF Core + Dapper architecture reference and data access patterns
- SKILLS.md - Comprehensive developer guides including data layer, views, API, services patterns
Key Architectural Decisions (2026-01-27):
- ✅ Phase 1: Reflection Logic Extraction (2026-01-27) -
IEntityOperationServicewith compiled delegates for 250x performance optimization - ✅ Phase 2: SQL-First View Pipeline (2026-01-27) -
IViewRegistry,IViewService,IDapperQueryServiceservices with 18 unit tests; Dapper ORM integration; ProductDashboard example component - ✅ Hybrid data access: EF Core for writes (200+ entities), Dapper for complex reads (SQL-first views)
- ✅ SQL-first everything: Both entities (DDL) and views (SELECT queries) start as SQL
- ✅ Single-project organization: Namespace-based separation (NOT 4 separate projects)
- ✅ Multi-tenancy: Finbuckle.MultiTenant with automatic schema inheritance for Dapper
- ✅ No Repository Pattern:
IEntityOperationService+IViewServiceprovide sufficient abstraction - ✅ Scale target: 200+ entities, multiple schemas, small team
Current Status: Architecture complete (Phase 1, 2, 3+4) - 192 tests passing; SQL-first view pipeline fully implemented
Unit tests are VERY IMPORTANT for this project. All new code must include comprehensive unit tests.
- Test-First Mindset: Write tests alongside or before implementation code
- No Untested Code: Every new service, generator, or significant change requires tests
- Run Tests Before Commit: Always run
make testbefore considering work complete - Test Coverage Target: 80%+ code coverage on service layer and generators
| Project | Purpose | Run Command |
|---|---|---|
tests/DotNetWebApp.Tests/ |
Services, Controllers, Integration | make test |
tests/ModelGenerator.Tests/ |
Path resolution, template validation | make test |
tests/DdlParser.Tests/ |
SQL parsing, type mapping, YAML generation | make test |
- All new services (IEntityOperationService, IViewService, IViewRegistry, etc.)
- Type mapping changes (TypeMapper.cs has 125+ tests)
- Code generators (ViewModelGenerator, EntityGenerator)
- YAML deserialization (ViewDefinition, AppDefinition classes)
- Controller endpoints (CRUD operations, validation)
- Multi-tenant scenarios (schema isolation)
make test # Run all tests (builds test projects first)
make build-all # Build including test projects
./dotnet-build.sh test tests/DdlParser.Tests/DdlParser.Tests.csproj --no-restore # Run specific project[Fact]
public async Task ServiceMethod_ValidInput_ReturnsExpectedResult()
{
// Arrange
var service = new MyService(mockDependency);
// Act
var result = await service.DoSomethingAsync(input);
// Assert
Assert.NotNull(result);
Assert.Equal(expected, result.Value);
}- Primary Goal: Use SQL DDL as the source of truth, generating
app.yamland C# models for dynamic customization. - Review
SESSION_SUMMARY.mdbefore starting work and update it when you make meaningful progress or decisions. - Build Optimizations: See
BUILD_OPTIMIZATION_SUMMARY.mdfor complete details on build performance improvements (30+ min → 2-5 min)
- Check/Setup:
make check(restore and build main projects - 4-8 min) - Build:
make build(fast Debug build, main projects only - 2-5 min) - Build All:
make build-all(includes test projects - 10-20 min, higher memory) - Build Release:
make build-release(production build - 10-20 min) - Run (dev):
make dev(with hot reload - use for active development) - Run (prod):
make run(without hot reload - use for production-like testing) - Test:
make test(build and run tests sequentially - 10-15 min) - Run DDL Pipeline:
make run-ddl-pipeline(unified pipeline: entities + views from sql/schema.sql + appsettings.json → app.yaml) - Apply Migration:
make migrate - Docker Build:
make docker-build - Clean:
make clean(cleans build outputs + stops build servers + stops dev sessions) - Stop Dev:
make stop-dev(kills orphaneddotnet watchprocesses) - Shutdown Build Servers:
make shutdown-build-servers(kills MSBuild/Roslyn processes)
Important: Default make build excludes test projects to prevent OOM errors. Use make build-all if you need tests built.
- The project uses a Makefile with the following targets:
check,build,dev,run,test,migrate,docker-build,clean,stop-dev,shutdown-build-servers - The dotnet-build.sh script is located in the project root and handles global.json SDK version conflicts
- Use
make <target>for standard operations - Use
./dotnet-build.sh <command>directly only for advanced dotnet CLI operations not covered by Makefile targets - Process cleanup: If you notice accumulating dotnet processes, run
make clean(full cleanup) or individuallymake stop-dev/make shutdown-build-servers
The project uses dotnet-build.sh wrapper script to handle SDK version conflicts between Windows and WSL environments. Different developers may have different .NET SDK versions installed (e.g., from Snap, apt-get, or native installers). The wrapper temporarily bypasses global.json version enforcement during local development, allowing flexibility while keeping the version specification in place for CI/CD servers.
For Windows + WSL developers: Install any supported .NET 8.x version locally. The wrapper script handles compatibility. CI/CD and production use the exact version specified in global.json.
DotNetWebApp/
├── sql/
│ ├── schema.sql # 📋 SQL DDL source (entities)
│ └── views/ # 🆕 SQL SELECT queries for complex views
│ ├── ProductSalesView.sql
│ └── ...
├── Controllers/ # API endpoints (EntitiesController, etc.)
├── Components/
│ ├── Pages/ # Routable Blazor pages (Home.razor, SpaApp.razor)
│ └── Sections/ # SPA components (Dashboard, Settings, Entity, etc.)
├── Data/
│ ├── AppDbContext.cs # EF Core DbContext with dynamic entity discovery
│ ├── DataSeeder.cs # Executes sql/seed.sql via EF
│ └── Dapper/ # 🆕 Dapper infrastructure (SQL view execution)
│ ├── IDapperQueryService.cs
│ └── DapperQueryService.cs
├── DotNetWebApp.Models/ # 🔄 Separate models assembly (extracted from main project)
│ ├── Generated/ # 🔄 Auto-generated entities from app.yaml (Product.cs, Category.cs, etc.)
│ ├── ViewModels/ # 🆕 Auto-generated view models from appsettings.json ViewDefinitions
│ ├── AppDictionary/ # YAML model classes (AppDefinition.cs, Entity.cs, Property.cs, etc.)
│ ├── AppCustomizationOptions.cs # App customization settings
│ ├── DashboardSummary.cs # Dashboard data model
│ ├── DataSeederOptions.cs # Data seeder configuration
│ ├── EntityMetadata.cs # Entity metadata record
│ ├── SpaSection.cs # SPA section model
│ └── SpaSectionInfo.cs # SPA section info model
├── Services/
│ ├── AppDictionaryService.cs # Loads and caches app.yaml
│ ├── IEntityMetadataService.cs # Maps YAML entities to CLR types
│ ├── EntityMetadataService.cs # Implementation
│ ├── IEntityOperationService.cs # ✅ EF CRUD operations (Phase 1 - COMPLETED 2026-01-27)
│ ├── EntityOperationService.cs # ✅ Implementation with compiled delegates (Phase 1 - COMPLETED 2026-01-27)
│ └── Views/ # 🆕 Dapper view services (Phase 2)
│ ├── IViewRegistry.cs
│ ├── ViewRegistry.cs
│ ├── IViewService.cs
│ └── ViewService.cs
├── Migrations/ # Generated EF Core migrations
├── Pages/ # Blazor host pages (_Host.cshtml, _Layout.cshtml)
├── Shared/ # Shared Blazor components (MainLayout.razor, NavMenu.razor, GenericEntityPage.razor, DynamicDataGrid.razor)
├── DdlParser/ # SQL DDL → YAML converter (separate console project)
│ ├── Program.cs
│ ├── SqlDdlParser.cs
│ ├── CreateTableVisitor.cs
│ ├── TypeMapper.cs
│ └── YamlGenerator.cs
├── ModelGenerator/ # YAML → C# generator (separate console project)
│ ├── EntityGenerator.cs # Entities from app.yaml (existing)
│ └── ViewModelGenerator.cs # 🆕 Views from appsettings.json ViewDefinitions
├── tests/
│ ├── DotNetWebApp.Tests/ # Unit/integration tests
│ └── ModelGenerator.Tests/ # Model generator path resolution tests
├── wwwroot/ # Static files (CSS, JS, images)
├── _Imports.razor # Global Blazor using statements
├── appsettings.json # Configuration: includes ViewDefinitions for view generation
├── app.yaml # 📋 Final runtime config (generated: entities + views)
├── sql/
│ ├── schema.sql # Source SQL DDL for testing DDL parser
│ ├── seed.sql # Seed data (Categories, Products)
│ └── views/ # SQL SELECT queries for views
├── Makefile # Build automation
├── dotnet-build.sh # .NET SDK version wrapper
├── HYBRID_ARCHITECTURE.md # EF+Dapper architecture reference
├── ARCHITECTURE_SUMMARY.md # Quick architecture overview
├── DotNetWebApp.sln # Solution file (includes all projects)
└── DotNetWebApp.csproj # Main project file
- DDL-driven data model: SQL DDL generates
app.yamland entity models - Model Generation:
ModelGeneratorreadsapp.yamland generates C# entities with nullable value types for optional fields - Modular Architecture: Models extracted to separate
DotNetWebApp.Modelsassembly for better separation of concerns - Dynamic Data Layer:
AppDbContextdiscovers entities via reflection and pluralizes table names (e.g.,Product→Products) - Dynamic Entity API:
EntitiesControllerprovides CRUD endpoints at/api/entities/{entityName}and/api/entities/{entityName}/count - Optional SPA example: Toggle the
/approutes viaAppCustomization:EnableSpaExampleinappsettings.json - Generic CRUD UI:
GenericEntityPage.razor+DynamicDataGrid.razorrender dynamic data grids from YAML definitions - Dynamic Navigation:
NavMenu.razorrenders "Data" section with schema-qualified links (e.g.,/entity/acme/Company) and labels showing schema for disambiguation - DDL to YAML Parser: Complete pipeline (DdlParser → app.yaml → ModelGenerator → DotNetWebApp.Models/Generated)
- Converts SQL Server DDL files to
app.yamlformat - Handles table definitions, constraints, foreign keys, IDENTITY columns, DEFAULT values
- Pipeline target:
make run-ddl-pipelineexecutes the full workflow
- Converts SQL Server DDL files to
- Entity Metadata Service:
IEntityMetadataServicemaps app.yaml entities to CLR types for API/UI reuse - Seed Data System:
DataSeederexecutessql/seed.sqlonce schema exists- Run with:
make seed - Guards against duplicate inserts
- Run with:
- Tenant Schema Support: Multi-schema via
X-Customer-Schemaheader (defaults todbo) - Unit Tests:
DotNetWebApp.Testscovers DataSeeder with SQLite-backed integration tests;ModelGenerator.Testsvalidates path resolution - Shell Script Validation:
make checkrunsshellcheckon setup.sh, dotnet-build.sh, and verify.sh - Build Passes:
make checkandmake buildpass;make testpasses with Release config - Build Optimization:
cleanup-nested-dirsMakefile target prevents inotify exhaustion on Linux systems - Docker Support: Makefile includes Docker build and SQL Server container commands
- Phase 1 - Reflection Logic Extraction (2026-01-27): ✅ COMPLETED
IEntityOperationServiceinterface centralizes all CRUD operationsEntityOperationServiceimplementation with compiled expression tree delegates- Cached compiled delegates provide 250x performance improvement (first call ~500μs, subsequent ~2μs)
- EntitiesController reduced from 369 to 236 lines (36% reduction)
- All reflection logic removed from controller (moved to service layer)
- Comprehensive test suite added (30+ tests for EntityOperationService)
- All existing tests passing (45 total tests across all projects)
- Generated models folder (
DotNetWebApp.Models/Generated/) is empty initially; populated bymake run-ddl-pipelineor manualModelGeneratorrun - Branding currently mixed between
appsettings.jsonandapp.yaml(could be fully moved to YAML) - Composite primary keys not supported in DDL parser (single column PKs only)
- CHECK and UNIQUE constraints ignored by DDL parser
- Computed columns ignored by DDL parser
- All Makefile targets working (
check,build,dev,run,test,migrate,seed,docker-build,db-start,db-stop,db-drop,stop-dev,shutdown-build-servers) dotnet-build.shwrapper manages .NET SDK version conflicts across Windows/WSL/Linuxmake migraterequires SQL Server running and valid connection string- Session tracking via
SESSION_SUMMARY.mdfor LLM continuity between sessions
- MSBuild node reuse:
dotnet buildspawns MSBuild node processes (/nodeReuse:true) that persist after builds. Usemake shutdown-build-serversto force-kill them. - dotnet build-server shutdown limitations: The
dotnet build-server shutdowncommand claims success but orphaned MSBuild/Roslyn processes may not actually terminate. Ourshutdown-build-serverstarget force-kills stuck processes after attempting graceful shutdown. - dotnet watch signal handling:
dotnet watchcatches SIGTERM (defaultkillsignal) for graceful shutdown but ignores it when orphaned/detached. Must usekill -9(SIGKILL) to terminate. Usemake stop-devwhich handles this correctly. - Zombie processes: Killed processes may become zombies (
<defunct>) until parent reaps them. These are harmless and don't consume resources. - Process accumulation: Running multiple
makecommands (especiallytest,run-ddl-pipeline) without cleanup causes dotnet process accumulation. Runmake cleanperiodically ormake stop-dev+make shutdown-build-serversas needed. - Wrapper script processes: The
dotnet-build.shwrapper may leave bash process entries after termination. These typically become zombies and don't need manual cleanup.
This project is DDL-driven. Everything flows from sql/schema.sql → app.yaml → generated C# models. Multiple schemas with identical table names (e.g., acme.Companies and initech.Companies) are perfectly valid SQL but require careful handling throughout the codebase.
When the same table name exists in multiple schemas, components must use schema-qualified names (schema:TableName) everywhere—not just plain table names. Failing to do so causes:
- Wrong data returned: API fetches
acme.Companywheninitech.Companywas requested - Type casting errors:
InvalidCastException: Unable to cast 'Acme.Company' to 'Initech.Company' - Dictionary key collisions:
An item with the same key has already been added. Key: Company
- Browser URLs:
schema/TableName(e.g.,/entity/acme/Company,/app/initech/Company) - uses slash, URL-safe - API endpoints:
schema:TableName(e.g.,/api/entities/acme:Company) - uses colon internally - C# Namespaces:
DotNetWebApp.Models.Generated.{PascalSchema}.{TableName}(e.g.,...Generated.Acme.Company) - YAML (app.yaml): Uses lowercase
schema:field (e.g.,schema: initech)
Important: Colons in URLs are interpreted by browsers as protocol schemes (like mailto:), causing xdg-open popups. Always use slashes for browser-facing URLs and convert to colons for API calls.
| File | What to use | NOT this |
|---|---|---|
EntityMetadataService.cs |
Pascal-cased schema in namespace: Generated.Initech.Company |
Generated.initech.Company |
DashboardService.cs |
$"{schema}:{name}" in both try AND catch blocks |
entity.Definition.Name |
EntitySection.razor |
EntityName parameter (colon format for API) |
metadata.Definition.Name |
GenericEntityPage.razor |
Convert URL Schema/EntityName to API schema:name |
Using URL format for API |
NavMenu.razor |
Build path as entity/{schema}/{name} (slash for URLs) |
Colons in browser URLs |
SpaSectionService.cs |
RouteSegment=schema/name, EntityName=schema:name |
Same format for both |
SpaApp.razor |
Convert URL slash format to API colon format | Using slash format for API |
URL Routing (use slashes):
// ✅ CORRECT - slash-separated for browser URLs
var path = $"entity/{entity.Schema}/{entity.Name}"; // "/entity/acme/Company"
// ❌ WRONG - colon triggers browser protocol handler popup
var path = $"{entity.Schema}:{entity.Name}"; // "acme:Company" causes xdg-open!API Calls (use colons):
// ✅ CORRECT - colon-separated for API calls
var qualifiedName = $"{schema}:{entityName}"; // "acme:Company"
var result = await EntityApiService.GetEntitiesAsync(qualifiedName);
// ❌ WRONG - strips schema, returns wrong data when duplicate table names exist
var result = await EntityApiService.GetEntitiesAsync(metadata.Definition.Name);verify.sh Test 12 validates multi-schema isolation by checking that acme:Company returns name field and initech:Company returns companyName field (they have different schemas with different properties).
- Hybrid architecture: ASP.NET Core Web API backend + Blazor Server SPA frontend
- SignalR connection: Real-time updates between client and server
- Entity Framework Core: Dynamic model registration via reflection; DbContext discovers entities at startup
- REST API design:
EntitiesControllerserves dynamic endpoints at/api/entities/{entityName} - UI architecture: Generic Blazor pages (
GenericEntityPage.razor) with reusable data grid components - YAML-driven generation:
ModelGeneratorreadsapp.yaml→ generates entities → migration generated for schema application - DDL parser pipeline: SQL Server DDL →
app.yaml→ C# entities → migration generation - Data model: All entities support IDENTITY primary keys, nullable value types for optional fields, foreign key relationships
- Multi-tenancy: Schema switching via
X-Customer-SchemaHTTP header - CSS: Global animations (pulse, spin, slideIn) in
wwwroot/css/app.css - Dependency injection: Services registered in
Program.cs(DbContext, AppDictionaryService, EntityMetadataService, DataSeeder)
- Project uses User Secrets for local development (see SECRETS.md for details)
- Connection strings stored in
~/.microsoft/usersecrets/, never in git setup.shscript automatically configures User Secrets when setting up SQL Server- Manual management:
dotnet user-secrets list,dotnet user-secrets set, etc.
| File | Purpose |
|---|---|
appsettings.json |
Configuration file with ViewDefinitions section; defines SQL views for code generation and app visibility |
app.yaml |
📋 Generated runtime configuration with entities + views (generated from sql/schema.sql + appsettings.json) |
DotNetWebApp.Models/ |
🔄 Separate models assembly containing all data models and configuration classes |
DotNetWebApp.Models/Generated/ |
🔄 Auto-generated C# entities (don't edit manually) |
DotNetWebApp.Models/ViewModels/ |
🔄 Auto-generated C# view models from appsettings.json ViewDefinitions (don't edit manually) |
DotNetWebApp.Models/AppDictionary/ |
YAML model classes for app.yaml structure |
sql/schema.sql |
SQL DDL source for entities; parsed by make run-ddl-pipeline |
sql/views/ |
SQL SELECT queries for views; referenced by appsettings.json ViewDefinitions |
sql/seed.sql |
Sample seed data INSERT statements for default schema; executed by make seed |
Data/AppDbContext.cs |
EF Core DbContext that discovers generated entities via reflection |
Services/AppDictionaryService.cs |
Loads and caches app.yaml for runtime access to entity definitions |
Services/IEntityMetadataService.cs |
Maps YAML entity names to CLR types for API/UI |
Controllers/EntitiesController.cs |
Dynamic controller providing CRUD endpoints for all entities |
Components/Shared/GenericEntityPage.razor |
Reusable page component for rendering any entity's CRUD UI |
Components/Shared/DynamicDataGrid.razor |
Dynamic data grid component that renders columns from YAML definitions |
Services/Views/ |
View services for Dapper-based read operations (IViewRegistry, IViewService, IDapperQueryService) |
DdlParser/ |
Console project: SQL DDL → YAML (standalone, not compiled into main app) |
ModelGenerator/ |
Console project: YAML → C# entities & view models (run separately when updating models) |
YamlMerger/ |
Console project: Merges appsettings.json ViewDefinitions into data.yaml during pipeline |
AppsYamlGenerator/ |
Console project: Merges Applications from appsettings.json with data.yaml to generate app.yaml |
Makefile |
Build automation with unified make run-ddl-pipeline target that handles entities + views |
dotnet-build.sh |
Wrapper script managing .NET SDK version conflicts across environments |
Recent commits show the project has evolved through:
- Foundation (earlier commits): Initial Blazor Server + API setup, Docker integration, self-signed certs
- Data Model Generation: Introduction of YAML-driven approach with ModelGenerator
- DDL Parser Pipeline: SQL DDL → YAML → C# entities workflow
- Entity Metadata Service: System for mapping YAML entities to CLR types
- Seed Data Implementation: Integration of sample data seeding
- Unit Tests: Test suite covering seed logic and integration scenarios
- Models Extraction (2026-01-25): Models moved to separate
DotNetWebApp.Modelsproject for better separation of concerns (commits:552127d,601f84d) - Build Optimization (2026-01-25): Added
cleanup-nested-dirsMakefile target to prevent inotify exhaustion on Linux - Documentation Expansion (2026-01-25): SKILLS.md significantly expanded with comprehensive guides; SESSION_SUMMARY.md simplified to documentation index
Latest work focuses on modular architecture and comprehensive developer documentation.
- Development occurs on both Windows and WSL (Ubuntu/Debian via apt-get)
- global.json specifies .NET 8.0.410 as the target version
- New developer setup: Run
./setup.sh, thenmake check,make db-start(if Docker),make run-ddl-pipeline, andmake migrate dotnet-build.shsetsDOTNET_ROOTfor global tools and temporarily hides global.json during executionmake checkrunsshellcheckon all shell scripts (setup.sh, dotnet-build.sh, verify.sh) before restore/buildmake migraterequires SQL Server running and a valid connection string;dotnet-efmay warn about version mismatchesmake cleanup-nested-dirsremoves nested project directories created by MSBuild to prevent inotify watch exhaustion on Linux (runs automatically aftermake build-allandmake test)- Makefile uses the wrapper script for consistency across all dotnet operations; do not modify the system .NET runtime
- Package versions use wildcards (
8.*) to support flexibility across different developer environments while maintaining .NET 8 compatibility - Models are in separate
DotNetWebApp.Modelsproject; YamlDotNet dependency lives there (removed from main project)