When building enterprise-grade applications with ASP.NET Core, the architecture decisions you make in the first weeks will echo through the project for years. Getting them right costs time upfront but pays back exponentially in reduced maintenance, easier scaling, and faster feature delivery.
Clean Architecture: The Non-Negotiable Foundation
Clean Architecture (popularised by Robert C. Martin) separates your application into concentric layers — Domain, Application, Infrastructure, and Presentation — where dependencies always point inward. In an ASP.NET Core context this means:
- Your Domain layer contains only entities, value objects, and business rules. Zero external dependencies.
- Your Application layer contains use cases and interfaces. It depends on Domain but nothing else.
- Your Infrastructure layer implements those interfaces — EF Core repositories, email senders, file storage.
- Your Presentation layer (the ASP.NET Core project) handles HTTP concerns and delegates everything else.
CQRS: Separating Reads from Writes
Command Query Responsibility Segregation (CQRS) is a powerful pattern that becomes almost mandatory at enterprise scale. The key insight: your read model and your write model have fundamentally different requirements. Reads are frequent, must be fast, and can tolerate eventual consistency. Writes must be correct, validated, and audited.
Using MediatR alongside CQRS keeps your controllers thin (they just dispatch commands and queries) and puts business logic where it belongs — in handlers that are easy to test in isolation.
Performance Best Practices
Response Caching and Output Caching
ASP.NET Core 7+ introduced Output Caching as a first-class feature. Use it for read-heavy endpoints that do not change per user. For user-specific data, consider Redis distributed caching with sliding expiration.
Async All the Way Down
Every I/O call — database, HTTP, file system — should be async. Blocking on async code (the classic .Result or .Wait() antipattern) kills scalability under load.
EF Core Query Optimisation
Use AsNoTracking() for read-only queries. Use Select() projections to fetch only the columns you need. Avoid N+1 queries by using Include() or split queries thoughtfully. Log slow queries with a custom ILoggerProvider or Application Insights.
Security Hardening
Enterprise applications are targets. Non-negotiable baseline:
- Enable HTTPS everywhere and configure HSTS with a long max-age
- Use the built-in CSRF protection for all form submissions
- Validate and sanitise all inputs — never trust user data
- Store secrets in Azure Key Vault or environment variables, never in appsettings.json
- Apply the Principle of Least Privilege to every service account and role
Structured Logging with Serilog
Replace the default ILogger with Serilog and write logs to both the console (for containerised deployments) and a structured sink like Seq or Application Insights. Enrich logs with correlation IDs so you can trace a request through every layer of a distributed system.
Share this article
Miguel is a full-stack developer with expertise in .NET, Angular, and cloud infrastructure. He leads the custom software team at NT4Solutions and advocates for clean code and DevOps culture.