ASP.NET Core  

Which NuGet packages are essential for building enterprise-grade ASP.NET Core apps using clean architecture

Introduction

Enterprise applications are fundamentally different from small or demo projects. They must be secure, scalable, resilient, observable, and easy to evolve over years.

ASP.NET Core provides a solid runtime and framework, but real enterprise readiness comes from choosing the right NuGet packages and placing them correctly inside Clean Architecture.

This article explains the most important .NET NuGet packages, why they are used in real-world systems, and how together they cover nearly 90% of enterprise application requirements.

Typical Layers

  • Domain Layer

    • Business entities

    • Core business rules

    • No framework dependencies

  • Application Layer

    • Use cases

    • CQRS (Commands & Queries)

    • Validation & orchestration logic

  • Infrastructure Layer

    • Database access

    • Logging

    • External services

    • Background processing

  • Presentation Layer (API/UI)

    • Controllers

    • Authentication

    • API documentation

    • Real-time communication

Each NuGet package should live only in the layer where it belongs.

1. Entity Framework Core

Layer: Infrastructure
Category: ORM / Data Access

Entity Framework Core (EF Core) is the most widely used ORM in the .NET ecosystem. It allows developers to work with relational databases using strongly typed C# classes instead of raw SQL.

Why enterprises use EF Core

  • Strong compile-time safety

  • Rich LINQ querying support

  • Database migrations

  • Change tracking and concurrency handling

  • Works well with Domain-Driven Design

Enterprise best practice

  • Use EF Core primarily for write operations

  • Keep DbContext inside Infrastructure

  • Expose repositories via interfaces

  • Avoid leaking EF Core entities into the API layer

EF Core significantly improves developer productivity while remaining performant when used correctly.

Example: DbContext (Infrastructure)

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options) { }

    public DbSet<Product> Products => Set<Product>();
}

Entity (Domain)

public class Product
{
    public Guid Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
}

Repository Implementation

public class ProductRepository : IProductRepository
{
    private readonly AppDbContext _context;

    public ProductRepository(AppDbContext context)
    {
        _context = context;
    }

    public async Task AddAsync(Product product)
    {
        _context.Products.Add(product);
        await _context.SaveChangesAsync();
    }
}

2. Dapper

Layer: Infrastructure
Category: Micro ORM / Performance

Dapper is a lightweight, high-performance data access library that maps SQL results directly to objects.

Why enterprises use Dapper

  • Extremely fast execution

  • No change tracking overhead

  • Full control over SQL

  • Ideal for complex reporting queries

Common enterprise pattern (Very Important)

EF Core for Commands (Writes)
Dapper for Queries (Reads)

This CQRS-based approach gives:

  • Maintainable writes

  • Ultra-fast reads

  • Better database performance at scale

Example: Read Models (Infrastructure)

public class ProductReadRepository
{
    private readonly IDbConnection _connection;

    public ProductReadRepository(IDbConnection connection)
    {
        _connection = connection;
    }

    public async Task<IEnumerable<ProductDto>> GetAllAsync()
    {
        var sql = "SELECT Id, Name, Price FROM Products";
        return await _connection.QueryAsync<ProductDto>(sql);
    }
}

3. MediatR

Layer: Application
Category: Architecture / Decoupling

MediatR implements the Mediator pattern, enabling clean separation between controllers, business logic, and infrastructure.

What problems it solves

  • Fat controllers

  • Tight coupling between layers

  • Hard-to-test services

Enterprise usage

  • Commands → Write operations

  • Queries → Read operations

  • Notifications → Cross-cutting events

MediatR is the backbone of Clean Architecture + CQRS in modern ASP.NET Core systems.

Example: Command

public record CreateProductCommand(string Name, decimal Price) : IRequest<Guid>;

Handler

public class CreateProductHandler : IRequestHandler<CreateProductCommand, Guid>
{
    private readonly IProductRepository _repository;

    public CreateProductHandler(IProductRepository repository)
    {
        _repository = repository;
    }

    public async Task<Guid> Handle(CreateProductCommand request, CancellationToken cancellationToken)
    {
        var product = new Product
        {
            Id = Guid.NewGuid(),
            Name = request.Name,
            Price = request.Price
        };

        await _repository.AddAsync(product);
        return product.Id;
    }
}

Controller

[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
    private readonly IMediator _mediator;

    public ProductsController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    public async Task<IActionResult> Create(CreateProductCommand command)
    {
        var id = await _mediator.Send(command);
        return Ok(id);
    }
}

4. FluentValidation

Layer: Application
Category: Validation

FluentValidation provides a clean and expressive way to define validation rules outside controllers.

Why enterprises prefer it

  • Keeps controllers thin

  • Centralized validation logic

  • Highly readable rules

  • Easy to unit test

Example

public class CreateProductCommandValidator : AbstractValidator<CreateProductCommand>
{
    public CreateProductCommandValidator()
    {
        RuleFor(x => x.Name)
            .NotEmpty()
            .MaximumLength(100);

        RuleFor(x => x.Price)
            .GreaterThan(0);
    }
}

5. Serilog

Layer: Infrastructure
Category: Logging & Observability

Serilog is a structured logging framework designed for production environments.

Why it matters

  • Logs are structured (JSON), not plain text

  • Easy integration with log aggregation tools

  • Context-rich logging for debugging production issues

Example: Program.cs

Log.Logger = new LoggerConfiguration()
    .Enrich.FromLogContext()
    .WriteTo.Console()
    .WriteTo.File("logs/app-.log", rollingInterval: RollingInterval.Day)
    .CreateLogger();

builder.Host.UseSerilog();

Usage

_logger.LogInformation("Product {ProductId} created successfully", product.Id);

6. Polly

Layer: Infrastructure
Category: Resilience & Fault Tolerance

Polly provides resilience policies for handling transient faults.

Problems it solves

  • Temporary network failures

  • Unstable third-party APIs

  • Microservice communication failures

Common policies

  • Retry with exponential backoff

  • Circuit breaker

  • Timeout

  • Fallback strategies

Example: HTTP Resilience

var retryPolicy = Policy
    .Handle<HttpRequestException>()
    .WaitAndRetryAsync(3, retry => TimeSpan.FromSeconds(retry));

builder.Services.AddHttpClient<IExternalService, ExternalService>()
    .AddPolicyHandler(retryPolicy);

7. Swashbuckle.AspNetCore (Swagger)

Layer: Presentation
Category: API Documentation

Swagger automatically generates interactive API documentation.

Why enterprises require it

  • Improves collaboration

  • Acts as a live API contract

  • Speeds up onboarding

  • Enables easier testing

Example

builder.Services.AddSwaggerGen();
app.UseSwagger();
app.UseSwaggerUI();

8. System.Text.Json

Layer: Presentation / Shared
Category: Serialization

System.Text.Json is the default JSON serializer in modern .NET.

Why it is preferred

  • High performance

  • Low memory usage

  • Built into the framework

  • No external dependency

Example

builder.Services.AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
        options.JsonSerializerOptions.WriteIndented = true;
    });