Monorepository with C#: How to Organize Code in .NET Projects
Modern development with C# and .NET increasingly faces a choice: store all related projects in one repository (monorepository) or split them into separate repositories (multirepository). A monorepository is an approach where the code of multiple applications, libraries, tests, and configurations is stored in a single Git repository. For large enterprise solutions, microservices, or NuGet libraries, this approach can significantly simplify dependency management, refactoring, and deployment.
In this article, we will examine how to properly organize a monorepository with C# in the .NET ecosystem, look at folder structure, project configuration, CI/CD, and best practices.
1. Advantages and Disadvantages of a Monorepository in .NET
Before diving into technical details, it is important to understand when a monorepository is truly useful and when it can become a problem.
Advantages:
- Single version of dependencies. All projects within the repository use the same package versions, eliminating version conflicts.
- Simplified refactoring. Changes to a common interface or library API are immediately visible in all projects that use it.
- Commit integrity. Atomic commits that change both the library and the application work without temporary breaks.
- Shared settings. One
Directory.Build.propsfile sets compilation settings, code analysis, and versions for all projects.
Disadvantages:
- Repository size growth. Over time, cloning and history can become very large.
- CI/CD complexity. A smart build system is required to avoid rebuilding everything on every commit.
- Access rights. It is more difficult to differentiate access for different teams within one repository.
For .NET projects, a monorepository is especially convenient when you have a shared class library (e.g., Domain or Common), several microservices or web APIs that depend on it.
2. Monorepository Structure for C# (.NET)
Proper folder structure is the foundation of a successful monorepository. Let's look at a typical organization for a solution that includes several microservices, a library, and tests.
MyApp/├── .github/│ └── workflows/│ └── ci.yml├── src/│ ├── MyApp.Common/ # shared library│ │ ├── MyApp.Common.csproj│ │ └── ...│ ├── MyApp.Api.Orders/ # microservice for orders│ │ ├── MyApp.Api.Orders.csproj│ │ └── ...│ └── MyApp.Api.Users/ # microservice for users│ ├── MyApp.Api.Users.csproj│ └── ...├── tests/│ ├── MyApp.Common.Tests/│ │ ├── MyApp.Common.Tests.csproj│ │ └── ...│ └── MyApp.Api.Orders.Tests/│ ├── MyApp.Api.Orders.Tests.csproj│ └── ...├── Directory.Build.props # shared settings for all projects├── MyApp.sln # solution for Visual Studio└── README.mdKey points:
- All projects are located in the
src/folder, tests intests/. This is a standard adopted in the .NET community. - The
Directory.Build.propsfile sets common properties:TargetFramework,Nullable,ImplicitUsings, package versions, etc. - One SLN file simplifies work in the IDE, but with a large number of projects, you can split them into multiple solutions.
Example Directory.Build.props:
<Project> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <TreatW