Production applications need more than CRUD. They need soft deletes so data is never truly lost, audit trails so you know who changed what and when, auto-generated readable identifiers, and domain modeling with enums and progress tracking. These cross-cutting concerns are where VSA's architecture is tested — do you sprinkle them across every handler, or do you build reusable infrastructure? The answer from both the Blazor CRM and MVC Project Manager is clear: build infrastructure once, use it everywhere.
The key insight is that cross-cutting concerns in VSA should be implemented as extension methods and interfaces that handlers can opt into. The SoftDelete extension on DbContext, the IHasAuditDisplay interface on DTOs, and the GenerateAutoNumberAsync method on DbContext are all reusable infrastructure that individual feature slices consume without coupling to each other. This keeps VSA's independence while providing consistent behavior across features.
Soft Delete with Cascade
The MVC Project Manager uses soft deletes everywhere. Records are never physically removed — they're marked as deleted with an IsDeleted flag and a DeletedAt timestamp. The SoftDelete extension method handles this for any entity. For cascade soft deletes, the handler loads the parent with all children using Include, then soft-deletes children first, then the parent. EF Core global query filters automatically exclude soft-deleted records from all queries — configured once in OnModelCreating and applies everywhere.
Automatic Audit Trail
Both implementations track who created and last modified each record. The Blazor CRM stores audit fields on the entity itself — CreatedAt, CreatedBy, UpdatedAt, UpdatedBy. These are populated automatically by the DbContext's SaveChangesAsync override using the current user service. The IHasAuditDisplay interface bridges the gap between stored user IDs and displayed user emails. DTOs implement this interface. After the query runs, a post-processing step resolves user IDs to emails. This keeps the main query fast (no JOIN to Users table) while providing human-readable audit data to the UI.
Auto-Number Generation
The Blazor CRM generates readable auto-numbers using a consonant-based short name template. For example, "Booking" becomes "BKG" and generates numbers like "BKG/2026/00001". This is vastly more user-friendly than GUIDs. The auto-number generation is an extension method on DbContext that handles concurrency (prevents duplicate numbers) and formatting:
var autoNo = await _context.GenerateAutoNumberAsync(
entityName: nameof(Data.Entities.Booking),
prefixTemplate: $"BKG/{{Year}}/",
ct: cancellationToken);
Domain Modeling with Enums and Progress
The MVC Project Manager models Todo domain concepts as enums: TodoPriority (Low, Medium, High) and TodoCategory (Personal, Work, Learning). These are stored as integers in the database and validated with FluentValidation's IsInEnum(). The progress field (0-100) is validated with InclusiveBetween(0, 100). Tags are stored as comma-separated strings with a 500-character limit. These domain concepts live in the feature's namespace, not in a shared "Domain" project. Each feature owns its enums, its validation rules, and its business logic — there's no shared enum that creates coupling between unrelated features. This is VSA's independence in action.