VSATutorialSeptember 2026 · 9 min read

VSA Todo App — Part 9: Soft Delete, Audit Trail & Cross-Cutting Concerns in VSA

TL;DR

Part 9 covers the infrastructure that makes VSA production-ready: soft delete with cascade, automatic audit trail (CreatedAt/CreatedBy/UpdatedAt/UpdatedBy), the IHasAuditDisplay interface for resolving audit emails, auto-number generation with consonant-based templates, progress tracking (0-100), and enum-based domain categorization (Priority, Category). All patterns are from real production code in the MVC Project Manager and Blazor CRM.

Part 9 of 20 in the VSA Todo App Tutorial Series|← Previous: Part 8|Next: Part 10 →

Help Us Grow

Love this tutorial? Explore our ready-to-use enterprise starter kits built with VSA in .NET 10.

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.

Key Takeaways

  • Soft delete with EF Core global query filters automatically excludes deleted records from all queries
  • Cascade soft delete loads the entity with children, soft-deletes children first, then the parent
  • IHasAuditDisplay resolves stored user IDs to display emails in a post-query processing step
  • Auto-number generation with consonant templates produces readable identifiers like "BKG/2026/00001"
  • Domain enums and progress tracking live in the feature namespace — each feature owns its domain concepts independently

Frequently Asked Questions

Q: Soft delete vs hard delete — which should I use in VSA?

Soft delete for production applications where data recovery and audit are important. Hard delete for simple apps or reference data. The MVC Project Manager uses soft delete everywhere with EF Core global query filters. The Blazor CRM uses hard delete for simplicity. Both patterns coexist in VSA — the handler decides.

Q: How to implement audit trail without cluttering VSA handlers?

Use EF Core's SaveChangesAsync override to automatically set CreatedAt/CreatedBy/UpdatedAt/UpdatedBy on every entity. Inject ICurrentUserService into the DbContext. Handlers don't need to set audit fields manually — the infrastructure handles it transparently.

Q: How to generate auto-numbers in VSA without race conditions?

Use a database-level approach: query the max existing number for the entity type and year, increment it, and save within a transaction. The Blazor CRM's GenerateAutoNumberAsync extension handles this. For high-concurrency scenarios, use a database sequence or an auto-number table with row-level locking.

Part 9 of 20 in the VSA Todo App Tutorial Series|← Previous: Part 8|Next: Part 10 →

Ready to study real VSA code?

Every Indotalent product is a complete .NET 10 application built with Vertical Slice Architecture. Complete source code — $21 each.

Explore Products

Looking for a ready-to-use traditional monolithic multilayered clean architecture?

Monolithic Clean Architecture — 1,300+ devs, 500+ forks. Clean Arch + CQRS + Repository Pattern. Free & open source for commercial use.

Star on GitHub