Skill v1.0.0
currentAutomated scanname: project-conventions description: This skill should be used when creating or editing .csproj files, managing NuGet packages, configuring Directory.Build.props or Directory.Packages.props, organizing .NET solutions, or setting up global.json and .editorconfig. version: 0.1.0
.NET Project Conventions and Build Configuration
This skill defines comprehensive conventions for .NET project structure, Central Package Management, build configuration, solution organization, and tooling setup.
SDK-Style .csproj Structure
Minimal .csproj for Source Projects
Source project files should be as minimal as possible. Properties shared across all projects belong in Directory.Build.props, not in each .csproj.
<!-- CORRECT: Minimal .csproj relying on Directory.Build.props --><Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><RootNamespace>Catalog.Application</RootNamespace></PropertyGroup><ItemGroup><ProjectReference Include="..\Catalog.Domain\Catalog.Domain.csproj" /></ItemGroup><ItemGroup><PackageReference Include="MediatR" /></ItemGroup></Project>
<!-- WRONG: Duplicating properties that belong in Directory.Build.props --><Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>net8.0</TargetFramework><Nullable>enable</Nullable><ImplicitUsings>enable</ImplicitUsings><TreatWarningsAsErrors>true</TreatWarningsAsErrors><LangVersion>latest</LangVersion><RootNamespace>Catalog.Application</RootNamespace></PropertyGroup><ItemGroup><ProjectReference Include="..\Catalog.Domain\Catalog.Domain.csproj" /></ItemGroup><ItemGroup><PackageReference Include="MediatR" Version="12.4.1" /></ItemGroup></Project>
Web API Project Uses Web SDK
<!-- CORRECT: Web SDK for ASP.NET Core projects --><Project Sdk="Microsoft.NET.Sdk.Web"><PropertyGroup><RootNamespace>Catalog.Api</RootNamespace></PropertyGroup></Project>
<!-- WRONG: Standard SDK with manual ASP.NET references --><Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><RootNamespace>Catalog.Api</RootNamespace><OutputType>Exe</OutputType></PropertyGroup><ItemGroup><FrameworkReference Include="Microsoft.AspNetCore.App" /></ItemGroup></Project>
Test Project Inherits from tests/Directory.Build.props
<!-- CORRECT: Test project with minimal configuration --><Project Sdk="Microsoft.NET.Sdk"><ItemGroup><ProjectReference Include="..\..\src\Catalog.Application\Catalog.Application.csproj" /></ItemGroup></Project>
<!-- WRONG: Test project duplicating shared test dependencies --><Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><IsPackable>false</IsPackable><IsTestProject>true</IsTestProject></PropertyGroup><ItemGroup><ProjectReference Include="..\..\src\Catalog.Application\Catalog.Application.csproj" /></ItemGroup><ItemGroup><PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" /><PackageReference Include="xunit" Version="2.9.2" /><PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" /><PackageReference Include="FluentAssertions" Version="6.12.2" /><PackageReference Include="NSubstitute" Version="5.3.0" /><PackageReference Include="coverlet.collector" Version="6.0.2" /></ItemGroup></Project>
Central Package Management (CPM)
Always Use Directory.Packages.props
All package versions must be declared centrally. Individual .csproj files reference packages without version numbers.
<!-- CORRECT: Directory.Packages.props declares all versions --><Project><PropertyGroup><ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally><CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled></PropertyGroup><ItemGroup><PackageVersion Include="MediatR" Version="12.4.1" /><PackageVersion Include="FluentValidation" Version="11.11.0" /><PackageVersion Include="xunit" Version="2.9.2" /></ItemGroup></Project>
<!-- CORRECT: .csproj references without Version --><ItemGroup><PackageReference Include="MediatR" /><PackageReference Include="FluentValidation" /></ItemGroup>
<!-- WRONG: Version on PackageReference when CPM is enabled --><ItemGroup><PackageReference Include="MediatR" Version="12.4.1" /></ItemGroup>
Group Packages by Category in CPM
<!-- CORRECT: Grouped and commented --><Project><PropertyGroup><ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally></PropertyGroup><!-- ASP.NET Core --><ItemGroup><PackageVersion Include="Swashbuckle.AspNetCore" Version="6.9.0" /><PackageVersion Include="Serilog.AspNetCore" Version="8.0.3" /></ItemGroup><!-- Entity Framework Core --><ItemGroup><PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.11" /><PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.11" /></ItemGroup><!-- Testing --><ItemGroup><PackageVersion Include="xunit" Version="2.9.2" /><PackageVersion Include="FluentAssertions" Version="6.12.2" /><PackageVersion Include="NSubstitute" Version="5.3.0" /></ItemGroup></Project>
<!-- WRONG: Flat, unsorted, no grouping --><Project><PropertyGroup><ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally></PropertyGroup><ItemGroup><PackageVersion Include="xunit" Version="2.9.2" /><PackageVersion Include="Swashbuckle.AspNetCore" Version="6.9.0" /><PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.11" /><PackageVersion Include="NSubstitute" Version="5.3.0" /><PackageVersion Include="Serilog.AspNetCore" Version="8.0.3" /></ItemGroup></Project>
Enable Transitive Pinning
Always enable CentralPackageTransitivePinningEnabled to control transitive dependency versions.
<!-- CORRECT: Transitive pinning enabled --><PropertyGroup><ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally><CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled></PropertyGroup>
<!-- WRONG: Missing transitive pinning --><PropertyGroup><ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally></PropertyGroup>
Directory.Build.props
Root Props for Shared Settings
Place at the solution root. Applies to all projects in the directory tree.
<!-- CORRECT: Shared properties at solution root --><Project><PropertyGroup><TargetFramework>net8.0</TargetFramework><Nullable>enable</Nullable><ImplicitUsings>enable</ImplicitUsings><TreatWarningsAsErrors>true</TreatWarningsAsErrors><LangVersion>latest</LangVersion><AnalysisLevel>latest-recommended</AnalysisLevel><EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild><Deterministic>true</Deterministic></PropertyGroup><ItemGroup><PackageReference Include="Meziantou.Analyzer" PrivateAssets="all" /><PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" PrivateAssets="all" /></ItemGroup></Project>
<!-- WRONG: Missing critical settings --><Project><PropertyGroup><TargetFramework>net8.0</TargetFramework></PropertyGroup></Project>
Test Props Import Parent Then Add Test-Specific Settings
<!-- CORRECT: tests/Directory.Build.props --><Project><Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" /><PropertyGroup><IsPackable>false</IsPackable><IsTestProject>true</IsTestProject><NoWarn>$(NoWarn);CA1707</NoWarn></PropertyGroup><ItemGroup><PackageReference Include="coverlet.collector" /><PackageReference Include="FluentAssertions" /><PackageReference Include="Microsoft.NET.Test.Sdk" /><PackageReference Include="NSubstitute" /><PackageReference Include="xunit" /><PackageReference Include="xunit.runner.visualstudio" /></ItemGroup></Project>
<!-- WRONG: Not importing parent props --><Project><PropertyGroup><TargetFramework>net8.0</TargetFramework><IsPackable>false</IsPackable><IsTestProject>true</IsTestProject></PropertyGroup><!-- Lost all parent settings: Nullable, TreatWarningsAsErrors, analyzers --></Project>
Solution Layout
Layered Architecture with src/ and tests/
CORRECT:project-root/├── src/│ ├── MyApp.Api/ (Web layer)│ ├── MyApp.Application/ (Business logic)│ ├── MyApp.Domain/ (Domain models)│ └── MyApp.Infrastructure/ (Data access, external)├── tests/│ ├── Directory.Build.props (test-specific)│ ├── MyApp.Application.Tests/│ ├── MyApp.Domain.Tests/│ └── MyApp.Api.Tests/├── Directory.Build.props (shared)├── Directory.Packages.props (CPM)├── global.json├── .editorconfig└── MyApp.sln
WRONG:project-root/├── MyApp.Api/├── MyApp.Application/├── MyApp.Domain/├── MyApp.Infrastructure/├── MyApp.Tests/ (single test project for everything)└── MyApp.sln
Test Project Mirrors Source Project
CORRECT:src/Catalog.Application/Services/ProductService.cstests/Catalog.Application.Tests/Services/ProductServiceTests.cssrc/Catalog.Domain/Models/Order.cstests/Catalog.Domain.Tests/Models/OrderTests.cs
WRONG:src/Catalog.Application/Services/ProductService.cstests/Tests/ProductServiceTest.cssrc/Catalog.Domain/Models/Order.cstests/Tests/OrderTests.cs
global.json
Always Pin SDK Version
{"sdk": {"version": "8.0.404","rollForward": "latestPatch","allowPrerelease": false}}
// WRONG: No global.json at all// Different developers may use different SDK versions,// causing inconsistent builds
Use latestPatch Roll-Forward
{"sdk": {"version": "8.0.404","rollForward": "latestPatch"}}
// WRONG: latestMajor allows any SDK{"sdk": {"version": "8.0.404","rollForward": "latestMajor"}}
.editorconfig
Must Exist at Solution Root
Every .NET solution must have an .editorconfig at the root. At minimum, it must set:
# CORRECT: Minimum .editorconfigroot = true[*]indent_style = spaceindent_size = 4end_of_line = lfcharset = utf-8trim_trailing_whitespace = trueinsert_final_newline = true[*.{csproj,props,targets}]indent_size = 2[*.cs]csharp_style_namespace_declarations = file_scoped:warningdotnet_style_qualification_for_field = false:warning
Enforce File-Scoped Namespaces
# CORRECT: Enforce file-scoped namespaces[*.cs]csharp_style_namespace_declarations = file_scoped:warning
# WRONG: No namespace style enforcement[*.cs]csharp_style_namespace_declarations = file_scoped:suggestion
Enforce Naming Conventions
# CORRECT: Naming rules enforced as errorsdotnet_naming_rule.interfaces_must_begin_with_i.severity = errordotnet_naming_rule.types_must_be_pascal_case.severity = errordotnet_naming_rule.private_fields_must_be_camel_case.severity = warning
# WRONG: Naming rules as suggestions (not enforced)dotnet_naming_rule.interfaces_must_begin_with_i.severity = suggestiondotnet_naming_rule.types_must_be_pascal_case.severity = suggestion
NuGet Publishing
Library Projects Include Package Metadata
<!-- CORRECT: Package metadata in .csproj for publishable library --><PropertyGroup><PackageId>Acme.Shared.Contracts</PackageId><Version>1.0.0</Version><Description>Shared contracts and DTOs for Acme services</Description><PackageLicenseExpression>MIT</PackageLicenseExpression><PackageReadmeFile>README.md</PackageReadmeFile><IncludeSymbols>true</IncludeSymbols><SymbolPackageFormat>snupkg</SymbolPackageFormat><PublishRepositoryUrl>true</PublishRepositoryUrl><EmbedUntrackedSources>true</EmbedUntrackedSources></PropertyGroup>
<!-- WRONG: Missing symbol packages and source link --><PropertyGroup><PackageId>Acme.Shared.Contracts</PackageId><Version>1.0.0</Version></PropertyGroup>
Non-Publishable Projects Are Marked IsPackable false
<!-- CORRECT: Test and API projects are not packaged --><PropertyGroup><IsPackable>false</IsPackable></PropertyGroup>
<!-- WRONG: Not setting IsPackable on non-library projects --><!-- dotnet pack would create a .nupkg for the API project -->
Target Framework
Use Latest LTS Framework
<!-- CORRECT: Latest LTS --><PropertyGroup><TargetFramework>net8.0</TargetFramework></PropertyGroup>
<!-- WRONG: End-of-life or preview framework --><PropertyGroup><TargetFramework>net6.0</TargetFramework></PropertyGroup>
Libraries May Multi-Target for Compatibility
<!-- CORRECT: Multi-target for broad compatibility --><PropertyGroup><TargetFrameworks>net8.0;net9.0</TargetFrameworks></PropertyGroup>
<!-- WRONG: Multi-target including unsupported frameworks --><PropertyGroup><TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks></PropertyGroup>
Analyzer Configuration
Analyzers Are Configured in Directory.Build.props
<!-- CORRECT: Analyzers in Directory.Build.props, applied to all projects --><ItemGroup><PackageReference Include="Meziantou.Analyzer" PrivateAssets="all" /><PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" PrivateAssets="all" /></ItemGroup>
<!-- WRONG: Analyzers added per project --><!-- In Catalog.Api.csproj --><ItemGroup><PackageReference Include="Meziantou.Analyzer" PrivateAssets="all" /></ItemGroup><!-- In Catalog.Application.csproj --><ItemGroup><PackageReference Include="Meziantou.Analyzer" PrivateAssets="all" /></ItemGroup>
Analyzer Severity in .editorconfig, Not .csproj
# CORRECT: Severity in .editorconfig[*.cs]dotnet_diagnostic.CA1062.severity = nonedotnet_diagnostic.CA2007.severity = nonedotnet_diagnostic.IDE0005.severity = warning
<!-- WRONG: Severity in .csproj --><PropertyGroup><NoWarn>$(NoWarn);CA1062;CA2007</NoWarn></PropertyGroup>
Build Determinism
Enable Deterministic Builds
<!-- CORRECT: Deterministic build in Directory.Build.props --><PropertyGroup><Deterministic>true</Deterministic><ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild></PropertyGroup>
<!-- WRONG: No determinism settings --><!-- Builds may produce different binaries for the same source -->
dotnet format Integration
Format Exclusions Are Applied Consistently
Always exclude generated code from formatting checks:
# CORRECT: Exclude generated codedotnet format --verify-no-changes --exclude obj/ --exclude Migrations/
# WRONG: No exclusions (fails on EF migrations)dotnet format --verify-no-changes
.gitignore Essentials
Must Ignore Build Artifacts and IDE Files
# CORRECT: Standard .NET .gitignore[Bb]in/[Oo]bj/TestResults/.vs/.idea/*.user*.suolaunchSettings.json
# WRONG: Missing critical entriesbin/obj/# Missing .vs/, TestResults/, *.user