A Single-Project Monolith is a deliberate structure in which an entire application — UI, domain logic, persistence, and public API — lives in one .NET project instead of being scattered across ten or fifteen class libraries. Most enterprise solutions start small and then accumulate projects: one for Domain, one for Application, one for Infrastructure, one for Persistence, plus a contracts project and a shared kernel. Each addition feels reasonable in isolation, but together they turn a simple codebase into a maze that no single developer fully holds in their head.
This article argues for the opposite move. Putting everything in one csproj file produces measurably better code: faster compiles, simpler dependency injection, fewer merge conflicts, and refactoring that actually works across the whole system. The trade-offs people fear — tangled dependencies and slow test runs — are mostly symptoms of bad organization, not of having a single project.
Why a Single-Project Monolith Compiles Faster
The .NET compiler does not optimize for aesthetics. MSBuild builds projects in dependency order, and every project boundary costs time: references must be resolved, metadata generated, and each output assembly checked for staleness. A ten-project solution pays that cost ten times over, and a change in a low-level project like Domain triggers rebuilds of every project that references it. In a single-project monolith, the whole application is one dependency graph, so incremental compilation covers exactly the files you touched and nothing more.
The difference is not theoretical. A developer on a multi-project solution spends real minutes staring at build cycles and fighting "cannot find type in referenced assembly" errors after a rename. With one project, an edit in the Orders feature and an edit in the Invoices feature rebuild against the same assembly, so hot reload and test feedback loops stay in seconds rather than minutes.
Simpler Dependency Injection in a Single-Project Monolith
Dependency injection is where multi-project solutions leak their complexity. Every project that wants MediatR, EF Core, or a mapper must reference the package, and the composition root must scan several assemblies to find handlers and services. Registration mistakes fail at runtime, not at compile time, and they are notoriously hard to trace.
A single-project monolith registers everything once, in one Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(
typeof(Program).Assembly));
builder.Services.AddDbContext<AppDbContext>(o =>
o.UseSqlServer(builder.Configuration
.GetConnectionString("Default")));
builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
var app = builder.Build();
app.MapOrderEndpoints();
app.MapInvoiceEndpoints();
app.Run();
One assembly means one call to RegisterServicesFromAssembly, one DbContext registration, and a single place where every dependency lifetime is decided. When a new developer asks where something is registered, the answer is always Program.cs. There is no ceremony, no scanning loop over fifteen assemblies, and no "forgot to register it in the right project" class of bug.
The Multi-Project Tax Nobody Budgets For
Beyond compile time, every project in a solution multiplies small costs that never appear in a project plan. Package versions must be kept in sync across ten csproj files, global usings and analyzers have to be configured per project, and the solution file itself becomes a source of merge conflicts as developers add and remove projects. Each cost is small; together they consume a measurable fraction of every sprint. A single-project monolith pays each of these costs exactly once.
Onboarding is where the tax is most visible. A new developer facing a fifteen-project solution must first learn which project owns what, then navigate the reference graph to answer a question like "where does an order get created?". In a single-project monolith, the answer is a folder path. The learning curve drops from weeks to days, which is why teams that collapse their solutions consistently report faster ramp-up alongside the faster builds.
Fewer Merge Conflicts and Easier Refactoring
Merge conflicts cluster around shared files, and shared files cluster around project boundaries. In a multi-project solution, DTOs and base classes live in their own projects, so every feature depends on the same handful of files. In a single-project monolith with vertical slice folders, two developers working on two features edit disjoint files, and merges become trivial.
Refactoring is where the single-project monolith wins largest. Renaming a shared concept such as Customer to Account in a multi-project solution means renaming project by project, with broken intermediate states and compile errors in each assembly. In one project, the codebase is a single compilation unit: rename once, and the compiler surfaces every affected site in one run. Roslyn analyzers and .NET 10 source generators behave better too, because they operate over one assembly graph instead of a web of fifteen.
None of this means the single-project monolith is right for every team. It means most teams reach for extra projects to solve organizational problems, and the cure usually makes the disease worse. Before you create your next class library, ask whether a folder would do.
Key Takeaways
- A Single-Project Monolith is one .NET project holding the whole application — no Domain, Application, and Infrastructure split.
- Fewer project boundaries mean faster incremental builds and shorter feedback loops.
- One assembly means one DI container, one Program.cs, and no cross-project registration bugs.
- Feature-based folders keep merge conflicts low and make whole-codebase refactoring a single operation.
- Every Indotalent product is a Single-Project Monolith built with Vertical Slice Architecture — complete .NET 10 source code for $21 each.
FAQ
Is a single-project monolith the same as a god project? No. A god project is a monolith with no internal structure. A single-project monolith uses folders and namespaces as enforced boundaries, so each feature stays cohesive and testable even though everything lives in one assembly.
How do I enforce boundaries with only one project? Structure by feature folders, use namespaces that mirror the folder layout, keep cross-feature calls behind interfaces, and let analyzers plus tests flag violations. The boundary is a convention the compiler and the test suite enforce, not a project reference.
Does a single-project monolith scale as the team grows? Up to a point, and better than people assume. Feature folders give teams independent areas of ownership. When cross-team coupling becomes the bottleneck, split the few genuinely independent modules out — do not scatter everything from the start.
Where do tests live? Tests are a different compilation target, so a single test project (or one per feature area) is normal and still leaves you with a single application project. The monolith refers to the application code, not the test harness.