Monorepo with C#: Practical Code Organization
\n\nIn modern C# (.NET) development, the question of choosing between a monorepo and a multirepo arises more and more often. A monorepo is an approach where the code of several projects, libraries, and services is stored in a single Git repository. In this article, we will look at how to organize a monorepo for C# solutions, covering practical examples of structure, build configuration, and dependency management.
\n\nBenefits of a Monorepo for C#
\n\nA monorepo is especially useful when you have several related projects: web APIs, console applications, class libraries, and tests. The main advantages are:
\n- \n
- Single version of dependencies — all projects use the same version of NuGet packages and internal libraries. \n
- Simplified refactoring — changing an API in one library is immediately visible to all consumers. \n
- Centralized CI/CD — one pipeline for building, testing, and deploying all components. \n
- Reduced duplication — shared models, utilities, and configurations are extracted into shared projects. \n
However, you should also consider the downsides: increased repository size, longer cloning times (if not using sparse checkout), and the need for a well-thought-out access control system.
\n\nMonorepo Structure in C#
\n\nLet's look at a typical monorepo structure for a .NET project. Suppose we have microservices AuthService, OrderService, and a shared library Shared.
MyMonorepo/\n├── .github/\n│ └── workflows/\n│ └── ci.yml\n├── src/\n│ ├── AuthService/\n│ │ ├── AuthService.Api/\n│ │ │ ├── Controllers/\n│ │ │ ├── Program.cs\n│ │ │ └── AuthService.Api.csproj\n│ │ └── AuthService.Tests/\n│ │ └── AuthService.Tests.csproj\n│ ├── OrderService/\n│ │ ├── OrderService.Api/\n│ │ │ └── OrderService.Api.csproj\n│ │ └── OrderService.Tests/\n│ │ └── OrderService.Tests.csproj\n│ └── Shared/\n│ ├── Shared.Models/\n│ │ └── Shared.Models.csproj\n│ └── Shared.Utils/\n│ └── Shared.Utils.csproj\n├── tests/\n│ └── IntegrationTests/\n│ └── IntegrationTests.csproj\n├── MyMonorepo.sln\n├── Directory.Build.props\n├── Directory.Packages.props\n├── .gitignore\n└── README.md\n\nKey elements:
\n- \n
- src/ — the main folder with source code. \n
- tests/ — integration tests, if they are not tied to a specific service. \n
- Directory.Build.props — common MSBuild settings for all projects. \n
- Directory.Packages.props — centralized management of NuGet package versions (Central Package Management mode). \n
- .sln — the solution file that unites all projects. \n
Setting Up Centralized Dependency Management
\n\nIn .NET 6+, built-in support for Central Package Management (CPM) was introduced. This is ideal for a monorepo. Create a Directory.Packages.props file in the root:
<Project>\n <PropertyGroup>\n <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>\n </PropertyGroup>\n <ItemGroup>\n <PackageVersion Include="Microsoft.AspNetCore.App" Version="8.0.0" />\n <PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />\n <PackageVersion Include="xunit" Version="2.7.0" />\n </ItemGroup>\n</Project>\n\nNow, in each .csproj, you only need to specify the package name without the version:
<Project Sdk="Microsoft.NET.Sdk.Web">\n <PropertyGroup>\n <TargetFramework>n