Skip to main content

C# Project Structure

Introduction​

A well-organized project structure is the foundation of any successful C# application. Just as a well-designed building needs a solid architectural plan, your code needs a thoughtful organization scheme. This guide will walk you through the essentials of structuring C# projects in a way that makes them maintainable, scalable, and easy to understand.

Whether you're creating a small console application or a complex enterprise system, following good project structure practices will save you and your team countless hours of frustration and make your codebase more professional.

Why Project Structure Matters​

Before diving into specifics, let's understand why project structure is crucial:

  • Maintainability: A logical structure makes code easier to update and fix
  • Readability: Others (and future you) can quickly understand how the application is organized
  • Scalability: A good structure allows your application to grow without becoming chaotic
  • Collaboration: Teams can work efficiently when the codebase is well-organized
  • Best Practices: Following conventions makes it easier to adopt other industry standards

Basic C# Project Structure​

Let's start with a simple console application and examine its structure:

csharp
// Program.cs - Entry point of the application
using System;

namespace MyFirstProject
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
}

Even this simple example follows a structure:

  • A namespace (MyFirstProject) that contains our code
  • A class (Program) that holds our methods
  • The Main method that serves as the application's entry point

Standard Directory Structure​

As your projects grow, you'll want to organize files into directories. Here's a common structure for a C# solution:

MySolution/
├── MySolution.sln # Solution file
├── src/ # Source code
│ ├── MyProject/ # Main project
│ │ ├── MyProject.csproj # Project file
│ │ ├── Program.cs # Entry point
│ │ ├── Models/ # Data models
│ │ ├── Services/ # Business logic
│ │ └── Utilities/ # Helper classes
│ └── MyProject.Tests/ # Test project
│ └── MyProject.Tests.csproj
└── docs/ # Documentation

This structure separates source code from other files and groups related code files into meaningful directories.

Creating a Multi-Project Solution​

For larger applications, you might want to split functionality across multiple projects. Let's explore a common structure using a simple e-commerce application as an example:

ECommerceApp/
├── ECommerceApp.sln
├── src/
│ ├── ECommerceApp.Core/ # Core domain models and logic
│ ├── ECommerceApp.Infrastructure/ # Data access, external services
│ ├── ECommerceApp.API/ # Web API project
│ └── ECommerceApp.Web/ # Web UI project
└── tests/
├── ECommerceApp.Core.Tests/
├── ECommerceApp.Infrastructure.Tests/
└── ECommerceApp.API.Tests/

Let's examine each project:​

  1. Core Project: Contains domain models, interfaces, and business logic
  2. Infrastructure Project: Implements data access, external service integration
  3. API Project: Hosts the web API endpoints
  4. Web Project: Contains the user interface

Project Organization by Feature vs. Type​

There are two main approaches to organizing code within a project:

Organization by Type​

MyProject/
├── Controllers/
│ ├── ProductsController.cs
│ └── OrdersController.cs
├── Models/
│ ├── Product.cs
│ └── Order.cs
├── Services/
│ ├── ProductService.cs
│ └── OrderService.cs
└── Repositories/
├── ProductRepository.cs
└── OrderRepository.cs

Organization by Feature​

MyProject/
├── Products/
│ ├── Product.cs
│ ├── ProductsController.cs
│ ├── ProductService.cs
│ └── ProductRepository.cs
└── Orders/
├── Order.cs
├── OrdersController.cs
├── OrderService.cs
└── OrderRepository.cs

For beginners, organizing by type is often simpler, but as projects grow, feature-based organization can make the codebase more navigable.

Practical Example: Building a Task Manager​

Let's apply these principles to create a task management application:

TaskManager/
├── TaskManager.sln
├── src/
│ ├── TaskManager.Core/
│ │ ├── Models/
│ │ │ ├── Task.cs
│ │ │ └── User.cs
│ │ └── Interfaces/
│ │ ├── ITaskRepository.cs
│ │ └── IUserRepository.cs
│ ├── TaskManager.Data/
│ │ ├── Repositories/
│ │ │ ├── TaskRepository.cs
│ │ │ └── UserRepository.cs
│ │ └── TaskManagerDbContext.cs
│ └── TaskManager.UI/
│ ├── Program.cs
│ └── TaskManagementService.cs
└── tests/
├── TaskManager.Core.Tests/
└── TaskManager.Data.Tests/

Let's examine the core model and interface:

csharp
// TaskManager.Core/Models/Task.cs
namespace TaskManager.Core.Models
{
public class Task
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public bool IsCompleted { get; set; }
public DateTime DueDate { get; set; }
public int UserId { get; set; }
}
}

// TaskManager.Core/Interfaces/ITaskRepository.cs
using TaskManager.Core.Models;

namespace TaskManager.Core.Interfaces
{
public interface ITaskRepository
{
Task GetById(int id);
IEnumerable<Task> GetAll();
IEnumerable<Task> GetByUser(int userId);
void Add(Task task);
void Update(Task task);
void Delete(int id);
}
}

And an implementation:

csharp
// TaskManager.Data/Repositories/TaskRepository.cs
using TaskManager.Core.Models;
using TaskManager.Core.Interfaces;

namespace TaskManager.Data.Repositories
{
public class TaskRepository : ITaskRepository
{
private readonly TaskManagerDbContext _context;

public TaskRepository(TaskManagerDbContext context)
{
_context = context;
}

public Task GetById(int id)
{
return _context.Tasks.Find(id);
}

public IEnumerable<Task> GetAll()
{
return _context.Tasks.ToList();
}

// Other methods implementation...
}
}

Naming Conventions​

Consistency in naming is crucial for maintainable code:

  • Solutions: CompanyName.ProductName
  • Projects: CompanyName.ProductName.Component
  • Namespaces: Match your project name
  • Classes: PascalCase (e.g., TaskRepository)
  • Interfaces: I prefix + PascalCase (e.g., ITaskRepository)
  • Files: Match the class name (e.g., TaskRepository.cs)

Best Practices​

  1. Keep the Entry Point Simple: The Program.cs or main file should do little more than bootstrap your application

  2. Separate Concerns: Each project should have a clear responsibility

  3. Avoid Circular References: Projects should only reference "downward" in your dependency hierarchy

  4. Follow the Dependency Inversion Principle: Core projects should not depend on infrastructure projects

  5. Group Related Files: Use folders to organize related files

  6. Be Consistent: Whatever structure you choose, apply it consistently

Common Pitfalls to Avoid​

  • Overly Deep Hierarchies: Too many nested folders make navigation difficult
  • Inconsistent Structure: Mixing different organization strategies
  • Monolithic Projects: Putting everything in one project makes maintenance harder
  • Breaking Encapsulation: Exposing implementation details outside their appropriate boundary
  • Incorrect Dependencies: Higher-level projects should not depend on lower-level ones

Summary​

A well-structured C# project provides a solid foundation for your application. By organizing your code logically, you'll make it easier to maintain, understand, and extend. Remember these key points:

  • Start with a clear separation of concerns
  • Choose between organization by type or feature
  • Use consistent naming conventions
  • Keep your entry point simple
  • Separate your core domain from infrastructure concerns

As your projects grow in complexity, revisit your structure periodically to ensure it still serves your needs. Refactoring a poor structure becomes more difficult the longer you wait.

Additional Resources​

Exercises​

  1. Create a simple console application with a structured approach for a library management system.
  2. Refactor an existing project to follow the best practices outlined in this guide.
  3. Create a multi-project solution for a blog system with separate projects for core, data, and presentation.
  4. Design a project structure for an e-commerce application with separate modules for products, orders, and customers.


If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)