DEV Community

Ravi Vishwakarma
Ravi Vishwakarma

Posted on

7 1 1 1

Clean Architecture in .net application step by step

Clean Architecture is a software design philosophy introduced by Robert C. Martin (Uncle Bob). Its goal is to create systems that are:

  • Independent of frameworks
  • Testable
  • Independent of UI or database
  • Flexible and maintainable

Key Principles

  1. Separation of Concerns: Each layer of the application has a specific responsibility and doesn't mix with others.
  2. Dependency Rule: Code dependencies must point inward, from outer layers (UI, DB) to inner layers (Use Cases, Entities). Inner layers know nothing about the outer layers.
  3. Inversion of Control: High-level policies (business rules) shouldn't depend on low-level details (DB, APIs). Instead, interfaces should be used to invert control.
  4. Independence:
    • Framework Independent: Can change from ASP.NET to another without affecting core logic.
    • Database Independent: Can switch from SQL Server to MongoDB.
    • UI Independent: Can change from Web to Mobile easily.

The Layers

  1. Domain Layer (Entities)
    • Contains core business rules
    • Framework and UI agnostic
    • Pure business objects (e.g., User, Order)
  2. Application Layer (Use Cases)
    • Contains application-specific logic
    • Coordinates the flow of data
    • Interfaces with repositories
    • E.g., CreateUser, GetOrders
  3. Interface Adapters Layer
    • Adapts data between use cases and frameworks
    • Includes Controllers, Gateways, ViewModels
    • Implements interfaces from Application Layer
  4. Frameworks & Drivers
    • External tools and frameworks (e.g., ASP.NET Core, EF Core)
    • Dependency injection and infrastructure
    • Least stable and most replaceable

Flow of Control

UI (Controller) → Use Case → Domain Logic → Output/Result

  • Controllers receive the input (e.g., HTTP request)
  • Pass data to the Use Case
  • Use Case performs business logic using the Domain
  • Returns results (e.g., DTOs or ViewModels)

⚖️ Advantages

  • High Testability – Business logic can be tested without UI or DB.
  • Easy to Maintain – Changes in UI/DB won’t affect core logic.
  • Scalability– Modular design helps teams work independently.
  • Reusability– Domain and Use Cases are reusable in different apps.

⚠️ Common Mistakes

  • Violating the dependency rule (e.g., use case calling Entity Framework directly)
  • Mixing business logic with framework code
  • Skipping interfaces (tight coupling)

🧱 Project Structure

Solution: CleanArchitectureDemo.sln
Projects:
├── CleanArchitectureDemo.Domain // Entities
├── CleanArchitectureDemo.Application // Use Cases
├── CleanArchitectureDemo.Infrastructure // DB / Repositories
├── CleanArchitectureDemo.API // Web API (Controllers)


🔧 Create Projects

dotnet new sln -n CleanArchitectureDemo

dotnet new classlib -n CleanArchitectureDemo.Domain
dotnet new classlib -n CleanArchitectureDemo.Application
dotnet new classlib -n CleanArchitectureDemo.Infrastructure
dotnet new webapi   -n CleanArchitectureDemo.API

Enter fullscreen mode Exit fullscreen mode

Add them to the solution:

dotnet sln add CleanArchitectureDemo.Domain
dotnet sln add CleanArchitectureDemo.Application
dotnet sln add CleanArchitectureDemo.Infrastructure
dotnet sln add CleanArchitectureDemo.API

Enter fullscreen mode Exit fullscreen mode

Add project references:

dotnet add CleanArchitectureDemo.Application reference CleanArchitectureDemo.Domain
dotnet add CleanArchitectureDemo.Infrastructure reference CleanArchitectureDemo.Application
dotnet add CleanArchitectureDemo.API reference CleanArchitectureDemo.Application
dotnet add CleanArchitectureDemo.API reference CleanArchitectureDemo.Infrastructure

Enter fullscreen mode Exit fullscreen mode

📁 Layer Implementations

1. Domain/Entities/User.cs

namespace CleanArchitectureDemo.Domain.Entities
{
    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
    }
}

Enter fullscreen mode Exit fullscreen mode

2. Application/Interfaces/IUserRepository.cs

using CleanArchitectureDemo.Domain.Entities;
using System.Collections.Generic;

namespace CleanArchitectureDemo.Application.Interfaces
{
    public interface IUserRepository
    {
        IEnumerable<User> GetAll();
        User GetById(int id);
    }
}

Enter fullscreen mode Exit fullscreen mode

3. Application/UseCases/UserService.cs

using CleanArchitectureDemo.Application.Interfaces;
using CleanArchitectureDemo.Domain.Entities;
using System.Collections.Generic;

namespace CleanArchitectureDemo.Application.UseCases
{
    public class UserService
    {
        private readonly IUserRepository _userRepository;

        public UserService(IUserRepository userRepository)
        {
            _userRepository = userRepository;
        }

        public IEnumerable<User> GetUsers() => _userRepository.GetAll();

        public User GetUser(int id) => _userRepository.GetById(id);
    }
}

Enter fullscreen mode Exit fullscreen mode

4. Infrastructure/Repositories/InMemoryUserRepository.cs

using CleanArchitectureDemo.Application.Interfaces;
using CleanArchitectureDemo.Domain.Entities;
using System.Collections.Generic;
using System.Linq;

namespace CleanArchitectureDemo.Infrastructure.Repositories
{
    public class InMemoryUserRepository : IUserRepository
    {
        private readonly List<User> _users = new()
        {
            new User { Id = 1, Name = "Alice", Email = "alice@example.com" },
            new User { Id = 2, Name = "Bob", Email = "bob@example.com" }
        };

        public IEnumerable<User> GetAll() => _users;

        public User GetById(int id) => _users.FirstOrDefault(u => u.Id == id);
    }
}

Enter fullscreen mode Exit fullscreen mode

5. API/Controllers/UserController.cs

using Microsoft.AspNetCore.Mvc;
using CleanArchitectureDemo.Application.UseCases;
using CleanArchitectureDemo.Domain.Entities;
using System.Collections.Generic;

namespace CleanArchitectureDemo.API.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class UserController : ControllerBase
    {
        private readonly UserService _userService;

        public UserController(UserService userService)
        {
            _userService = userService;
        }

        [HttpGet]
        public IEnumerable<User> Get() => _userService.GetUsers();

        [HttpGet("{id}")]
        public ActionResult<User> Get(int id)
        {
            var user = _userService.GetUser(id);
            if (user == null)
                return NotFound();
            return user;
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

6. API/Program.cs – Register Dependencies

using CleanArchitectureDemo.Application.Interfaces;
using CleanArchitectureDemo.Application.UseCases;
using CleanArchitectureDemo.Infrastructure.Repositories;

var builder = WebApplication.CreateBuilder(args);

// Dependency Injection
builder.Services.AddScoped<IUserRepository, InMemoryUserRepository>();
builder.Services.AddScoped<UserService>();

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

app.UseSwagger();
app.UseSwaggerUI();

app.MapControllers();
app.Run();

Enter fullscreen mode Exit fullscreen mode

Thanks
Keep Coding.

Dynatrace image

Frictionless debugging for developers

Debugging in production doesn't have to be a nightmare.

Dynatrace reimagines the developer experience with runtime debugging, native OpenTelemetry support, and IDE integration allowing developers to stay in the flow and focus on building instead of fixing.

Learn more

Top comments (0)

AWS Q Developer image

Build your favorite retro game with Amazon Q Developer CLI in the Challenge & win a T-shirt!

Feeling nostalgic? Build Games Challenge is your chance to recreate your favorite retro arcade style game using Amazon Q Developer’s agentic coding experience in the command line interface, Q Developer CLI.

Participate Now

👋 Kindness is contagious

Explore this insightful write-up embraced by the inclusive DEV Community. Tech enthusiasts of all skill levels can contribute insights and expand our shared knowledge.

Spreading a simple "thank you" uplifts creators—let them know your thoughts in the discussion below!

At DEV, collaborative learning fuels growth and forges stronger connections. If this piece resonated with you, a brief note of thanks goes a long way.

Okay