| name | project-structure |
| description | .NET solution and project structure conventions. Covers .slnx format, Directory.Build.props, Directory.Packages.props for central package management, global usings, and naming conventions. Load this skill when setting up a new solution, adding projects, configuring build properties, or when the user mentions "solution structure", ".slnx", "Directory.Build.props", "central package management", "Directory.Packages.props", "global usings", ".editorconfig", "project layout", or "naming conventions".
|
Project Structure
Core Principles
- Central package management — Use
Directory.Packages.props to manage NuGet package versions in one place. No version numbers in individual .csproj files.
- Shared build properties — Use
Directory.Build.props for common settings (target framework, nullable, implicit usings). Don't repeat in every project.
- .slnx for solutions — The new XML-based solution format is cleaner and more merge-friendly than the legacy
.sln format.
- src/tests separation — Source projects in
src/, test projects in tests/. Clear boundary.
Patterns
Solution Layout
MyApp/
├── MyApp.slnx # Solution file
├── Directory.Build.props # Shared MSBuild properties
├── Directory.Packages.props # Central package management
├── .editorconfig # Code style rules
├── .gitignore
├── global.json # SDK version pinning
├── src/
│ ├── MyApp.Api/ # Web API (entry point)
│ │ ├── MyApp.Api.csproj
│ │ ├── Program.cs
│ │ └── Features/
│ ├── MyApp.Domain/ # Domain entities, value objects (optional)
│ │ └── MyApp.Domain.csproj
│ └── MyApp.Infrastructure/ # EF Core, external services (optional)
│ └── MyApp.Infrastructure.csproj
└── tests/
└── MyApp.Api.Tests/
└── MyApp.Api.Tests.csproj
Directory.Build.props
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>14</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
</Project>
Directory.Packages.props (Central Package Management)
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Mediator.Abstractions" Version="3.0.0" />
<PackageVersion Include="Mediator.SourceGenerator" Version="3.0.0" />
<PackageVersion Include="FluentValidation.DependencyInjectionExtensions" Version="12.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.10.0" />
<PackageVersion Include="xunit.v3" Version="1.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.0" />
<PackageVersion Include="Testcontainers.PostgreSql" Version="4.0.0" />
</ItemGroup>
</Project>
Project File (.csproj) with Central Package Management
<Project Sdk="Microsoft.NET.Sdk.Web">
<ItemGroup>
<PackageReference Include="Mediator.Abstractions" />
<PackageReference Include="Mediator.SourceGenerator" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" />
<PackageReference Include="Microsoft.EntityFrameworkCore" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
<PackageReference Include="Serilog.AspNetCore" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyApp.Domain\MyApp.Domain.csproj" />
<ProjectReference Include="..\MyApp.Infrastructure\MyApp.Infrastructure.csproj" />
</ItemGroup>
</Project>
global.json (SDK Pinning)
{
"sdk": {
"version": "10.0.100",
"rollForward": "latestFeature"
}
}
.slnx Solution Format
<Solution>
<Folder Name="/src/">
<Project Path="src/MyApp.Api/MyApp.Api.csproj" />
<Project Path="src/MyApp.Domain/MyApp.Domain.csproj" />
<Project Path="src/MyApp.Infrastructure/MyApp.Infrastructure.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/MyApp.Api.Tests/MyApp.Api.Tests.csproj" />
</Folder>
</Solution>
Naming Conventions
| Element | Convention | Example |
|---|
| Solution | CompanyName.AppName or AppName | MyApp.slnx |
| Project | AppName.Layer | MyApp.Api, MyApp.Domain |
| Namespace | Matches folder path | MyApp.Api.Features.Orders |
| Feature folder | PascalCase, plural | Features/Orders/ |
| Test project | ProjectName.Tests | MyApp.Api.Tests |
Anti-patterns
Don't Scatter Package Versions
<PackageReference Include="Mediator.Abstractions" Version="2.0.0" />
<PackageReference Include="Mediator.Abstractions" Version="3.0.0" />
Don't Repeat Build Properties
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
Don't Mix Source and Test Projects
# BAD — tests mixed with source
src/
MyApp.Api/
MyApp.Api.Tests/ # test project in src/
# GOOD — clear separation
src/
MyApp.Api/
tests/
MyApp.Api.Tests/
Decision Guide
| Scenario | Recommendation |
|---|
| New solution | .slnx format |
| Package version management | Directory.Packages.props (central) |
| Shared build settings | Directory.Build.props |
| SDK version pinning | global.json |
| Common using directives | Global usings in Directory.Build.props |
| Small API (1-2 devs) | Single project (MyApp.Api) |
| Medium API (3-5 devs) | 2-3 projects (Api, Domain, Infrastructure) |
| Large / modular app | Module-per-project with shared Contracts |