From csharp-foundation
.NET SDK and NuGet tooling conventions: dotnet CLI commands (new/build/run/test/publish/restore/format), NuGet package management (PackageReference, Directory.Packages.props central package management, packages.lock.json), project file conventions (.csproj, .sln, global.json, Directory.Build.props), multi-targeting, and dotnet format. Stack-agnostic — referenced by every .NET plugin in the marketplace. Use this skill to: - Detect the .NET SDK version and run all commands via the dotnet CLI. - Manage NuGet dependencies safely (central package management, no floating versions). - Configure project files and solution-wide properties in Directory.Build.props. - Format code consistently with dotnet format. Do NOT use this skill for: - Framework-specific tooling (dotnet ef migrations, aspnet-codegenerator — those are in aspnet-core-plugin:aspnet-conventions). - Testing patterns — see csharp-foundation:dotnet-testing. - C# language idioms — see csharp-foundation:csharp-conventions.
How this skill is triggered — by the user, by Claude, or both
Slash command
/csharp-foundation:dotnet-toolingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Determine the project structure at the start of every task:
Determine the project structure at the start of every task:
| Signal | Meaning |
|---|---|
*.sln exists | Solution file — multiple projects; use dotnet build <solution>.sln |
Single *.csproj in root | Single-project layout |
global.json exists | SDK version is pinned — read it first |
Directory.Build.props exists | Solution-wide MSBuild properties apply |
Directory.Packages.props exists | Central Package Management is active — do not specify versions in individual .csproj files |
Always run dotnet commands from the directory containing the .sln or .csproj (or pass the path explicitly).
# Restore NuGet packages
dotnet restore
# Build (all projects in the solution, or a single project)
dotnet build
dotnet build MyApp.sln
dotnet build src/MyApp/MyApp.csproj
# Run (application project)
dotnet run --project src/MyApp/MyApp.csproj
# Run tests
dotnet test
dotnet test --filter "Category=Unit"
dotnet test --logger "trx;LogFileName=results.trx"
# Publish (Release, self-contained optional)
dotnet publish -c Release -o ./publish
dotnet publish -c Release --runtime linux-x64 --self-contained
# Check outdated packages
dotnet list package --outdated
# Format code (respects .editorconfig)
dotnet format
# Verify formatting without writing changes (useful in CI)
dotnet format --verify-no-changes
{
"sdk": {
"version": "8.0.404",
"rollForward": "latestPatch"
}
}
Always read global.json first to learn which SDK version is in use. Do not recommend commands or features that require a higher SDK version than what is pinned.
rollForward: "latestPatch" allows minor patch upgrades automatically — safe for CI. Use "disable" for strict reproducibility.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <!-- or net9.0, net10.0 -->
<Nullable>enable</Nullable> <!-- always enable -->
<ImplicitUsings>enable</ImplicitUsings> <!-- reduces boilerplate using directives -->
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <!-- recommended for new projects -->
<AnalysisLevel>latest</AnalysisLevel> <!-- Roslyn analyzers at latest rules set -->
</PropertyGroup>
<ItemGroup>
<!-- With Central Package Management: version goes in Directory.Packages.props -->
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Newtonsoft.Json" />
<!-- Without CPM: pin versions explicitly — no floating ranges -->
<!-- <PackageReference Include="Serilog" Version="4.1.0" /> -->
</ItemGroup>
</Project>
Never use floating version ranges (*, 1.*, [1.0,)) — they break reproducible builds. Pin exact or minimum patch versions.
# Add a package
dotnet add package Serilog --version 4.1.0
dotnet add src/MyApp/MyApp.csproj package FluentValidation
# Remove a package
dotnet remove package Serilog
# Inspect the dependency graph
dotnet list package
dotnet list package --include-transitive
dotnet list package --outdated
Microsoft.Extensions.* for DI, logging, configuration).When Directory.Packages.props exists, do not specify Version= attributes in individual .csproj files — the central file owns all version pins.
<!-- Directory.Packages.props (at solution root) -->
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
<PackageVersion Include="FluentValidation" Version="11.11.0" />
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="FluentAssertions" Version="6.12.1" />
</ItemGroup>
</Project>
Add new packages with:
# When CPM is active, dotnet add package still updates Directory.Packages.props
dotnet add package NewPackage --version 1.2.3
<!-- Directory.Build.props (at solution root) — applies to ALL projects -->
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AnalysisLevel>latest</AnalysisLevel>
<Authors>Your Org</Authors>
<Copyright>© 2025 Your Org</Copyright>
</PropertyGroup>
</Project>
Do not repeat properties already in Directory.Build.props in individual .csproj files — they are inherited automatically.
When a library must support multiple runtimes:
<PropertyGroup>
<TargetFrameworks>net6.0;net8.0</TargetFrameworks> <!-- semicolon-separated -->
</PropertyGroup>
Use #if NET8_0_OR_GREATER preprocessor symbols to conditionally compile version-specific code.
dotnet format respects .editorconfig and Roslyn analyzer rules. Run it after writing code:
# Fix all formatting issues in place
dotnet format
# Only fix whitespace issues (fastest)
dotnet format whitespace
# Only fix style issues (var usage, using directives, etc.)
dotnet format style
# CI gate — fails if any changes would be made
dotnet format --verify-no-changes
Create a .editorconfig at the solution root to enforce consistent style. Minimum recommended settings:
root = true
[*.cs]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8-bom
trim_trailing_whitespace = true
insert_final_newline = true
# Prefer file-scoped namespaces
csharp_style_namespace_declarations = file_scoped:warning
# Prefer var when type is apparent
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = false:suggestion
Enable lock files when reproducible restores are required (CI, Docker images):
<!-- .csproj or Directory.Build.props -->
<PropertyGroup>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
Commit packages.lock.json alongside source. In CI, restore with --locked-mode to fail on any drift:
dotnet restore --locked-mode
npx claudepluginhub aratkruglik/claude-sdlc --plugin csharp-foundationGuidelines for organizing .NET projects, including solution structure, project references, folder conventions, .slnx format, centralized build properties, and central package management. Use when setting up a new .NET solution with modern best practices, configuring centralized build properties across multiple projects, implementing central package version management, or setting up SourceLink for debugging.
Defines .NET solution and project structure conventions including .slnx format, Directory.Build.props, and central package management. Useful when setting up new solutions or configuring build properties.
Idiomatic C# /.NET development. Use when writing C# code, changing `.csproj` or `.sln`, or working on ASP.NET Core apps, libraries, CLIs, workers, and xUnit/NUnit/MSTest suites. Emphasizes nullable references, async/await, LINQ discipline, boundary validation, focused `dotnet` feedback, and minimal dependencies. NOT for Go, Python, TypeScript, shell scripts, or infra-only work.