Building Your First REST API with ASP.NET Core
Minimal APIs, Entity Framework Core, and FluentValidation — a real, working ASP.NET Core API without controller-class ceremony.
Clean Architecture organizes an ASP.NET Core application so business logic has zero dependency on frameworks, databases, or UI — meaning your core rules can be tested, and even reused, completely independent of how the app is currently built or deployed.
Clean Architecture is built around one rule: dependencies only point inward. The innermost layer (Domain) knows nothing about anything outside it. Each layer out from there can depend on layers further in, never the reverse.
Domain ← no dependencies on anything
Application ← depends on Domain only
Infrastructure ← depends on Application + Domain (implements interfaces they define)
Web (API) ← depends on all of the above, wires everything together
// Domain/Entities/Order.cs
public class Order
{
public int Id { get; private set; }
public OrderStatus Status { get; private set; }
public void MarkAsShipped()
{
if (Status != OrderStatus.Paid)
throw new InvalidOperationException("Only paid orders can ship.");
Status = OrderStatus.Shipped;
}
}
No Entity Framework attributes, no DbContext reference, no HTTP concepts anywhere in this file — it's plain C# that could compile with zero external packages.
// Application/Interfaces/IOrderRepository.cs
public interface IOrderRepository
{
Task<Order?> GetByIdAsync(int id);
Task SaveAsync(Order order);
}
// Application/UseCases/ShipOrder.cs
public class ShipOrderHandler
{
private readonly IOrderRepository _repo;
public ShipOrderHandler(IOrderRepository repo) => _repo = repo;
public async Task Handle(int orderId)
{
var order = await _repo.GetByIdAsync(orderId)
?? throw new NotFoundException();
order.MarkAsShipped();
await _repo.SaveAsync(order);
}
}
The Application layer defines what a repository must do (IOrderRepository) without knowing HOW — that's Infrastructure's job.
// Infrastructure/Repositories/EfOrderRepository.cs
public class EfOrderRepository : IOrderRepository
{
private readonly AppDbContext _db;
public EfOrderRepository(AppDbContext db) => _db = db;
public Task<Order?> GetByIdAsync(int id) =>
_db.Orders.FindAsync(id).AsTask();
public Task SaveAsync(Order order) =>
_db.SaveChangesAsync();
}
Swap Entity Framework for Dapper, or MySQL for PostgreSQL, and only this file changes — the Domain and Application layers never notice.
builder.Services.AddScoped<IOrderRepository, EfOrderRepository>();
builder.Services.AddScoped<ShipOrderHandler>();
The real payoff: ShipOrderHandler's tests inject a fake, in-memory IOrderRepository — no database, no HTTP server, no test infrastructure at all, just fast, isolated unit tests of your actual business rule.
Minimal APIs, Entity Framework Core, and FluentValidation — a real, working ASP.NET Core API without controller-class ceremony.
A complete JWT authentication setup for ASP.NET Core — issuing tokens, protecting endpoints, and role-based authorization.
Creating, applying, and safely rolling back EF Core migrations — the .NET equivalent of Laravel migrations, and where the workflow differs.