--- name: project-structure description: Guidelines 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. --- # .NET Project Structure and Build Configuration ## When to Use This Skill Use this skill when: - Setting up a new .NET solution with modern best practices - Configuring centralized build properties across multiple projects - Implementing central package version management - Setting up SourceLink for debugging and NuGet packages - Automating version management with release notes - Pinning SDK versions for consistent builds --- ## Recommended Solution Layout ``` MyApp/ ├── .config/ │ └── dotnet-tools.json # Local .NET tools ├── .editorconfig ├── .gitignore ├── global.json ├── nuget.config ├── Directory.Build.props ├── Directory.Build.targets ├── Directory.Packages.props ├── MyApp.slnx # .NET 9+ SDK / VS 17.13+ ├── src/ │ ├── MyApp.Core/ │ │ └── MyApp.Core.csproj │ ├── MyApp.Api/ │ │ ├── MyApp.Api.csproj │ │ ├── Program.cs │ │ └── appsettings.json │ └── MyApp.Infrastructure/ │ └── MyApp.Infrastructure.csproj └── tests/ ├── MyApp.UnitTests/ │ └── MyApp.UnitTests.csproj └── MyApp.IntegrationTests/ └── MyApp.IntegrationTests.csproj ``` Key principles: - Separate `src/` and `tests/` directories - One project per concern (Core/Domain, Infrastructure, API/Host) - Solution file at the repo root - All shared build configuration at the repo root --- ## Solution File Formats ### .slnx (Modern — .NET 9+) The XML-based solution format is human-readable and diff-friendly. Requires .NET 9+ SDK or Visual Studio 17.13+. ```xml ``` ### Migrating from .sln to .slnx ```bash dotnet sln MySolution.sln migrate ``` **Important:** Do not keep both `.sln` and `.slnx` files in the same repository. ### Creating a New .slnx Solution ```bash # .NET 10+: Creates .slnx by default dotnet new sln --name MySolution # .NET 9: Specify the format explicitly dotnet new sln --name MySolution --format slnx dotnet sln add src/MyApp/MyApp.csproj ``` ### Benefits - Dramatically fewer merge conflicts - Human-readable and editable - Consistent with modern `.csproj` format - Better diff/review experience in pull requests --- ## Directory.Build.props Shared MSBuild properties applied to all projects in the directory subtree. ```xml Your Team Your Company Copyright © 2020-$([System.DateTime]::Now.Year) Your Company Your Product https://github.com/yourorg/yourrepo https://github.com/yourorg/yourrepo Apache-2.0 latest enable enable true true latest-all $(NoWarn);CS1591 netstandard2.0 net8.0 net9.0 true true true snupkg embedded true logo.png README.md ``` ### Nested Directory.Build.props Inner files do **not** automatically import outer files: ```xml ``` --- ## Directory.Build.targets Imported **after** project evaluation. Use for: - Shared analyzer package references - Custom build targets - Conditional logic based on project type ```xml ``` --- ## Directory.Packages.props - Central Package Management CPM centralizes all NuGet package versions at the repo root. Individual `.csproj` files reference packages **without** a `Version` attribute. ```xml true true 1.5.35 9.1.0 ``` ### Consuming Packages (No Version Needed) ```xml ``` ### Version Overrides ```xml ``` --- ## .editorconfig Place at the repo root to enforce consistent code style: ```ini root = true [*] indent_style = space indent_size = 4 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.{csproj,props,targets,xml,json,yml,yaml}] indent_size = 2 [*.cs] csharp_style_namespace_declarations = file_scoped:warning csharp_prefer_braces = true:warning csharp_style_var_for_built_in_types = true:suggestion csharp_style_var_when_type_is_apparent = true:suggestion dotnet_style_require_accessibility_modifiers = always:warning csharp_style_prefer_pattern_matching = true:suggestion csharp_style_prefer_switch_expression = true:suggestion csharp_using_directive_placement = outside_namespace:warning dotnet_sort_system_directives_first = true dotnet_naming_rule.private_fields_should_be_camel_case.symbols = private_fields dotnet_naming_rule.private_fields_should_be_camel_case.style = camel_case_underscore dotnet_naming_rule.private_fields_should_be_camel_case.severity = warning dotnet_naming_symbols.private_fields.applicable_kinds = field dotnet_naming_symbols.private_fields.applicable_accessibilities = private dotnet_naming_style.camel_case_underscore.required_prefix = _ dotnet_naming_style.camel_case_underscore.capitalization = camel_case ``` --- ## global.json - SDK Version Pinning ```json { "sdk": { "version": "9.0.200", "rollForward": "latestFeature" } } ``` ### Roll Forward Policies | Policy | Behavior | |--------|----------| | `disable` | Exact version required | | `patch` | Same major.minor, latest patch | | `feature` | Same major, latest minor.patch | | `latestFeature` | Same major, latest feature band | | `minor` | Same major, latest minor | | `latestMinor` | Same major, latest minor | | `major` | Latest SDK (not recommended) | **Recommended:** `latestFeature` - Allows patch updates within the same feature band. --- ## nuget.config Configure package sources and security: ```xml ``` The `` + explicit sources + `` pattern prevents supply-chain attacks. For private feeds: ```xml ``` --- ## NuGet Audit .NET 9+ enables `NuGetAudit` by default: ```xml true low all ``` --- ## Lock Files Enable deterministic restores: ```xml true ``` In CI: ```bash dotnet restore --locked-mode ``` --- ## SourceLink and Deterministic Builds For libraries published to NuGet: ```xml true true embedded true ``` --- ## Version Management with RELEASE_NOTES.md ```markdown #### 1.2.0 January 15th 2025 #### - Added new feature X - Fixed bug in Y #### 1.1.0 December 10th 2024 #### - Initial release ``` ### CI/CD Integration ```yaml - name: Update version from release notes shell: pwsh run: ./build.ps1 - name: Build run: dotnet build -c Release - name: Pack with tag version run: dotnet pack -c Release /p:PackageVersion=${{ github.ref_name }} ``` --- ## Quick Reference | File | Purpose | |------|---------| | `MySolution.slnx` | Modern XML solution file | | `Directory.Build.props` | Centralized build properties | | `Directory.Packages.props` | Central package version management | | `global.json` | SDK version pinning | | `NuGet.Config` | Package source configuration | | `RELEASE_NOTES.md` | Version history | | `.editorconfig` | Code style enforcement | | `.config/dotnet-tools.json` | Local .NET tools | --- ## References - [.NET Library Design Guidance](https://learn.microsoft.com/en-us/dotnet/standard/library-guidance/) - [Central Package Management](https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management) - [.slnx Format](https://learn.microsoft.com/en-us/visualstudio/ide/reference/solution-file) - [Directory.Build.props](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-your-build) - [SourceLink](https://learn.microsoft.com/en-us/dotnet/standard/library-guidance/sourcelink) - [NuGet Audit](https://learn.microsoft.com/en-us/nuget/concepts/auditing-packages)