DEV Community

Ravi Vishwakarma
Ravi Vishwakarma

Posted on

6 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.

Top comments (0)

Image of Stellar post

Check out Episode 1: How a Hackathon Project Became a Web3 Startup 🚀

Ever wondered what it takes to build a web3 startup from scratch? In the Stellar Dev Diaries series, we follow the journey of a team of developers building on the Stellar Network as they go from hackathon win to getting funded and launching on mainnet.

Read more

👋 Kindness is contagious

Dive into this insightful write-up, celebrated within the collaborative DEV Community. Developers at any stage are invited to contribute and elevate our shared skills.

A simple "thank you" can boost someone’s spirits—leave your kudos in the comments!

On DEV, exchanging ideas fuels progress and deepens our connections. If this post helped you, a brief note of thanks goes a long way.

Okay