Monorepository with C#: Code Organization in Practice

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Monorepository with C#: Code Organization in Practice



A monorepository (monorepo) is an approach to source code management where all projects of a company or team are stored in a single repository. In the world of C# and .NET, this approach is becoming increasingly popular due to simplified collaborative development, unified dependency versions, and centralized CI/CD. However, without proper code organization, a monorepository can turn into chaos. In this article, we will explore practical techniques for organizing C# code in a monorepository: from folder structure to build configuration.



1. Folder Structure: The Foundation of Order



The first step to a successful monorepository is a well-thought-out directory hierarchy. It is recommended to follow a convention where each project resides in a separate folder, and common components are placed in src/shared. An example of a basic structure:



/monorepo├── src/│   ├── shared/│   │   ├── CommonLib/          # Common utilities, DTOs, interfaces│   │   └── LoggingLib/         # Logging│   ├── services/│   │   ├── AuthService/        # Authentication microservice│   │   └── OrderService/       # Order microservice│   └── apps/│       ├── WebApi/             # Main web application│       └── ConsoleApp/         # Console tool├── tests/│   ├── unit/│   │   ├── CommonLib.Tests/│   │   └── AuthService.Tests/│   └── integration/│       └── IntegrationTests/├── docs/├── scripts/├── .gitignore├── Directory.Build.props└── global.json


Key points:

  • src/ — source code for all projects.
  • shared/ — libraries used by multiple projects.
  • services/ — microservices or modules.
  • apps/ — end applications (Web API, Console, Blazor).
  • tests/ — tests, isolated from the source code.


This structure makes it easy to find code, avoid circular dependencies, and simplifies CI/CD setup.



2. Managing Projects with Directory.Build.props



In a monorepository, there is often a need to set common settings for all projects: .NET versions, common packages, code analysis rules. Instead of duplicating these in each .csproj, use the Directory.Build.props file in the repository root. Example:



<Project>  <PropertyGroup>    <TargetFramework>net8.0</TargetFramework>    <ImplicitUsings>enable</ImplicitUsings>    <Nullable>enable</Nullable>    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>  </PropertyGroup>

<ItemGroup> <PackageReference Include="SonarAnalyzer.CSharp" Version="9.*" /> </ItemGroup></Project>


Now each child project automatically inherits these settings. If you need to override them for a specific project, simply add the properties to its .csproj — they take priority.



Additionally, you can create Directory.Build.targets for common build targets (e.g., copying files after the build).



3. Dependency Management: A Centralized Approach



One of the main challenges of a monorepository is package version conflicts. In .NET 8, built-in support for Central Package Management (CPM) was introduced. To use it, create a Directory.Packages.props file in the root:



<Project>  <PropertyGroup>    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>  </PropertyGroup>  <ItemGroup>    <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />    <PackageVersion Include="Serilog" Version="3.1.1" /&g                

Blogs

Book Recommendations