ASP.NET Core  

Best Practices for Structuring Large ASP.NET Projects: A Simple Guide

πŸ”§ 1. Use a Clean Architecture (Onion or Hexagonal)

Separate concerns clearly with layered architecture:

Layers:

  • Presentation/UI (ASP.NET MVC/Razor Pages/API Controllers)
  • Application (Use cases, commands, queries, interfaces)
  • Domain (Entities, value objects, business rules)
  • Infrastructure (Database, file system, email, external APIs)

Example Folder Structure:

/src

  • /Web (Presentation Layer)
  • /Application
  • /Domain
  • /Infrastructure
  • /tests
  • /UnitTests
  • /IntegrationTests

🧱 2. Modularize by Feature, Not Type

Avoid grouping files by technical type (e.g., Controllers, Models, Views). Instead, use feature folders:

/Features

/Orders
  OrderController.cs
  OrderService.cs
  Order.cs

/Products
  ProductController.cs
  ProductService.cs
  Product.cs

This improves encapsulation and makes features easier to test and modify.

πŸ“¦ 3. Use Dependency Injection Everywhere

Rely on ASP.NET Core’s built-in DI system. Avoid static classes or service locators.

Register interfaces and implementations in Startup.cs or via extension methods in Infrastructure.

πŸ§ͺ 4. Separate Test Projects

Maintain dedicated test projects for unit and integration tests, mirroring the structure of the main app.

/tests
  /App.UnitTests
  /App.IntegrationTests

πŸ“‚ 5. Use Projects for Separation

Split into multiple class libraries:

  • MyApp.Web
  • MyApp.Application
  • MyApp.Domain
  • MyApp.Infrastructure

This enforces compile-time separation and better boundaries.

πŸ”„ 6. Follow SOLID Principles

  • Single Responsibility: One class = one job.
  • Open/Closed: Code open for extension, closed for modification.
  • Liskov: Use base classes/interfaces safely.
  • Interface Segregation: Prefer small interfaces.
  • Dependency Inversion: Depend on abstractions.

Here are the essential folder, solution, and project structures for large ASP.NET applications:

Solution Structure

Multi-Project Solution Layout:

MySolution.sln
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ MyApp.Domain/
β”‚   β”œβ”€β”€ MyApp.Application/
β”‚   β”œβ”€β”€ MyApp.Infrastructure/
β”‚   β”œβ”€β”€ MyApp.Web.API/
β”‚   β”œβ”€β”€ MyApp.Web.MVC/
β”‚   └── MyApp.Shared/
β”œβ”€β”€ tests/
β”‚   β”œβ”€β”€ MyApp.Domain.Tests/
β”‚   β”œβ”€β”€ MyApp.Application.Tests/
β”‚   β”œβ”€β”€ MyApp.Infrastructure.Tests/
β”‚   └── MyApp.Integration.Tests/
β”œβ”€β”€ docs/
β”œβ”€β”€ scripts/
└── tools/

For Microservices/Multiple Bounded Contexts:

MySolution.sln
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ Services/
β”‚   β”‚   β”œβ”€β”€ MyApp.Orders/
β”‚   β”‚   β”‚   β”œβ”€β”€ MyApp.Orders.API/
β”‚   β”‚   β”‚   β”œβ”€β”€ MyApp.Orders.Domain/
β”‚   β”‚   β”‚   └── MyApp.Orders.Infrastructure/
β”‚   β”‚   β”œβ”€β”€ MyApp.Users/
β”‚   β”‚   └── MyApp.Inventory/
β”‚   β”œβ”€β”€ Shared/
β”‚   β”‚   β”œβ”€β”€ MyApp.Shared.Kernel/
β”‚   β”‚   β”œβ”€β”€ MyApp.Shared.Infrastructure/
β”‚   β”‚   └── MyApp.Shared.Contracts/
β”‚   └── Gateways/
β”‚       └── MyApp.API.Gateway/

Project Structure Patterns

Clean Architecture Project Layout:

MyApp.Domain/
β”œβ”€β”€ Entities/
β”œβ”€β”€ ValueObjects/
β”œβ”€β”€ Enums/
β”œβ”€β”€ Interfaces/
β”œβ”€β”€ Events/
β”œβ”€β”€ Exceptions/
└── Specifications/

MyApp.Application/
β”œβ”€β”€ Commands/
β”œβ”€β”€ Queries/
β”œβ”€β”€ Handlers/
β”œβ”€β”€ Services/
β”œβ”€β”€ Interfaces/
β”œβ”€β”€ DTOs/
β”œβ”€β”€ Mappings/
└── Validators/

MyApp.Infrastructure/
β”œβ”€β”€ Data/
β”‚   β”œβ”€β”€ Configurations/
β”‚   β”œβ”€β”€ Migrations/
β”‚   └── Repositories/
β”œβ”€β”€ Services/
β”œβ”€β”€ External/
└── Caching/

MyApp.Web.API/
β”œβ”€β”€ Controllers/
β”œβ”€β”€ Middleware/
β”œβ”€β”€ Filters/
β”œβ”€β”€ Models/
β”‚   β”œβ”€β”€ Requests/
β”‚   └── Responses/
β”œβ”€β”€ Configuration/
└── Extensions/

Feature-Based Structure (Alternative):

MyApp.Web.API/
β”œβ”€β”€ Features/
β”‚   β”œβ”€β”€ Orders/
β”‚   β”‚   β”œβ”€β”€ GetOrder/
β”‚   β”‚   β”‚   β”œβ”€β”€ GetOrderQuery.cs
β”‚   β”‚   β”‚   β”œβ”€β”€ GetOrderHandler.cs
β”‚   β”‚   β”‚   └── GetOrderValidator.cs
β”‚   β”‚   β”œβ”€β”€ CreateOrder/
β”‚   β”‚   └── OrdersController.cs
β”‚   β”œβ”€β”€ Users/
β”‚   └── Products/
β”œβ”€β”€ Common/
β”‚   β”œβ”€β”€ Behaviors/
β”‚   β”œβ”€β”€ Exceptions/
β”‚   └── Extensions/
└── Infrastructure/

Folder Organization Within Projects

MyApp.Domain/
β”œβ”€β”€ Aggregates/
β”‚   β”œβ”€β”€ Order/
β”‚   β”‚   β”œβ”€β”€ Order.cs
β”‚   β”‚   β”œβ”€β”€ OrderItem.cs
β”‚   β”‚   └── IOrderRepository.cs
β”‚   └── User/
β”œβ”€β”€ Common/
β”‚   β”œβ”€β”€ BaseEntity.cs
β”‚   β”œβ”€β”€ IAggregateRoot.cs
β”‚   └── DomainEvent.cs
β”œβ”€β”€ ValueObjects/
β”‚   β”œβ”€β”€ Money.cs
β”‚   β”œβ”€β”€ Address.cs
β”‚   └── Email.cs
└── Exceptions/
    └── DomainException.cs

Infrastructure Project Structure:

MyApp.Infrastructure/
β”œβ”€β”€ Data/
β”‚   β”œβ”€β”€ Context/
β”‚   β”‚   └── ApplicationDbContext.cs
β”‚   β”œβ”€β”€ Configurations/
β”‚   β”‚   β”œβ”€β”€ OrderConfiguration.cs
β”‚   β”‚   └── UserConfiguration.cs
β”‚   β”œβ”€β”€ Repositories/
β”‚   β”‚   β”œβ”€β”€ OrderRepository.cs
β”‚   β”‚   └── BaseRepository.cs
β”‚   └── Migrations/
β”œβ”€β”€ Services/
β”‚   β”œβ”€β”€ EmailService.cs
β”‚   └── FileStorageService.cs
β”œβ”€β”€ External/
β”‚   β”œβ”€β”€ PaymentGateway/
β”‚   └── NotificationService/
└── Caching/
    └── RedisCacheService.cs

API Project Structure:

MyApp.Web.API/
β”œβ”€β”€ Controllers/
β”‚   β”œβ”€β”€ V1/
β”‚   β”‚   β”œβ”€β”€ OrdersController.cs
β”‚   β”‚   └── UsersController.cs
β”‚   └── V2/
β”œβ”€β”€ Models/
β”‚   β”œβ”€β”€ Requests/
β”‚   β”‚   β”œβ”€β”€ CreateOrderRequest.cs
β”‚   β”‚   └── UpdateOrderRequest.cs
β”‚   β”œβ”€β”€ Responses/
β”‚   β”‚   └── OrderResponse.cs
β”‚   └── Common/
β”‚       └── PagedResponse.cs
β”œβ”€β”€ Middleware/
β”‚   β”œβ”€β”€ ExceptionHandlingMiddleware.cs
β”‚   └── RequestLoggingMiddleware.cs
β”œβ”€β”€ Filters/
β”‚   └── ValidateModelFilter.cs
β”œβ”€β”€ Configuration/
β”‚   β”œβ”€β”€ DependencyInjection.cs
β”‚   └── SwaggerConfiguration.cs
└── Extensions/
    └── ServiceCollectionExtensions.cs

Specialized Structures

MyApp.BackgroundServices/
β”œβ”€β”€ Jobs/
β”‚   β”œβ”€β”€ OrderProcessingJob.cs
β”‚   └── EmailSenderJob.cs
β”œβ”€β”€ Services/
β”œβ”€β”€ Configuration/
└── Extensions/

For Shared Libraries:

MyApp.Shared.Kernel/
β”œβ”€β”€ Extensions/
β”œβ”€β”€ Utilities/
β”œβ”€β”€ Constants/
β”œβ”€β”€ Attributes/
└── Interfaces/

MyApp.Shared.Contracts/
β”œβ”€β”€ Events/
β”œβ”€β”€ Commands/
β”œβ”€β”€ Queries/
└── DTOs/

Project Dependencies

Dependency Flow:

  • Web.API → Application + Infrastructure
  • Application → Domain only
  • Infrastructure → Domain + Application (for interfaces)
  • Domain → No dependencies
  • Shared.Kernel → No dependencies
  • Shared.Contracts → Shared.Kernel only

File Naming Conventions

Controllers: OrdersController.cs, UsersController.cs Services: IOrderService.cs, OrderService.cs Repositories: IOrderRepository.cs, OrderRepository.cs DTOs: CreateOrderDto.cs, OrderResponseDto.cs Commands/Queries: CreateOrderCommand.cs, GetOrderQuery.cs Handlers: CreateOrderCommandHandler.cs, GetOrderQueryHandler.cs

Configuration Files Organization

MyApp.Web.API/
β”œβ”€β”€ appsettings.json
β”œβ”€β”€ appsettings.Development.json
β”œβ”€β”€ appsettings.Production.json
β”œβ”€β”€ appsettings.Staging.json
└── Configuration/
    β”œβ”€β”€ DatabaseSettings.cs
    β”œβ”€β”€ JwtSettings.cs
    └── ExternalServiceSettings.cs

This structure provides clear separation of concerns, makes navigation intuitive for large teams, and scales well as your application grows. The key is consistency across all projects and establishing clear naming conventions that everyone follows.

 

Founded in 2003, Mindcracker is the authority in custom software development and innovation. We put best practices into action. We deliver solutions based on consumer and industry analysis.