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
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
Common enterprise pattern (Very Important)
EF Core for Commands (Writes)
Dapper for Queries (Reads)
This CQRS-based approach gives:
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
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
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
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
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;
});