Monorepo with C#: Practical Code Organization

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

Monorepo with C#: Practical Code Organization

\n\n

In 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\n

Benefits of a Monorepo for C#

\n\n

A 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
\n\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\n

Monorepo Structure in C#

\n\n

Let's look at a typical monorepo structure for a .NET project. Suppose we have microservices AuthService, OrderService, and a shared library Shared.

\n\n
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\n

Key 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
\n\n

Setting Up Centralized Dependency Management

\n\n

In .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:

\n\n
<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\n

Now, in each .csproj, you only need to specify the package name without the version:

\n\n
<Project Sdk="Microsoft.NET.Sdk.Web">\n  <PropertyGroup>\n    <TargetFramework>n                

Blogs

Book Recommendations