--- name: dotnet-project-structure description: Modern .NET project structure including .slnx solution format, Directory.Build.props, central package management, SourceLink, version management with RELEASE_NOTES.md, and SDK pinning with global.json. invocable: false --- # .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 ## Related Skills - **`dotnet-local-tools`** - Managing local .NET tools with dotnet-tools.json - **`microsoft-extensions-configuration`** - Configuration validation patterns --- ## Solution File Format (.slnx) The `.slnx` format is the modern XML-based solution file format introduced in .NET 9. It replaces the traditional `.sln` format. ### Benefits Over Traditional .sln | Aspect | .sln (Legacy) | .slnx (Modern) | |--------|---------------|----------------| | Format | Custom text format | Standard XML | | Readability | GUIDs, cryptic syntax | Clean, human-readable | | Version control | Hard to diff/merge | Easy to diff/merge | | Editing | IDE required | Any text editor | ### Version Requirements | Tool | Minimum Version | |------|-----------------| | .NET SDK | 9.0.200 | | Visual Studio | 17.13 | | MSBuild | Visual Studio Build Tools 17.13 | **Note:** Starting with .NET 10, `dotnet new sln` creates `.slnx` files by default. In .NET 9, you must explicitly migrate or specify the format. ### Example .slnx File ```xml ``` ### Migrating from .sln to .slnx Use the `dotnet sln migrate` command to convert existing solutions: ```bash # Migrate a specific solution file dotnet sln MySolution.sln migrate # Or if only one .sln exists in the directory, just run: dotnet sln migrate ``` **Important:** Do not keep both `.sln` and `.slnx` files in the same repository. This causes issues with automatic solution detection and can lead to sync problems. After migration, delete the old `.sln` file. You can also migrate in Visual Studio: 1. Open the solution 2. Select the Solution in Solution Explorer 3. Go to **File > Save Solution As...** 4. Change "Save as type" to **Xml Solution File (*.slnx)** ### 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 # Add projects (works the same for both formats) dotnet sln add src/MyApp/MyApp.csproj ``` ### Recommendation **If you're using .NET 9.0.200 or later, migrate your solutions to .slnx.** The benefits are significant: - Dramatically fewer merge conflicts (no random GUIDs changing) - Human-readable and editable in any text editor - Consistent with modern `.csproj` format - Better diff/review experience in pull requests --- ## Directory.Build.props `Directory.Build.props` provides centralized build configuration that applies to all projects in a directory tree. Place it at the solution root. ### Complete Example ```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 your;tags;here latest enable enable true $(NoWarn);CS1591 1.0.0 See RELEASE_NOTES.md netstandard2.0 net8.0 net9.0 true true true snupkg logo.png README.md ``` ### Key Patterns #### Dynamic Copyright Year ```xml Copyright © 2020-$([System.DateTime]::Now.Year) Your Company ``` Uses MSBuild property functions to insert current year at build time. No manual updates needed. #### Reusable Target Framework Properties Define target frameworks once, reference everywhere: ```xml net8.0 net9.0 $(NetLibVersion) $(NetTestVersion) ``` #### SourceLink for NuGet Packages SourceLink enables step-through debugging of NuGet packages: ```xml true true true snupkg ``` --- ## Directory.Packages.props - Central Package Management Central Package Management (CPM) provides a single source of truth for all NuGet package versions. ### Setup ```xml true 1.5.35 9.1.0 ``` ### Consuming Packages (No Version Needed) ```xml ``` ### Benefits 1. **Single source of truth** - All versions in one file 2. **No version drift** - All projects use same versions 3. **Easy updates** - Change once, applies everywhere 4. **Grouped packages** - Version variables for related packages (e.g., all Akka packages) --- ## global.json - SDK Version Pinning Pin the .NET SDK version for consistent builds across all environments. ```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. --- ## Version Management with RELEASE_NOTES.md ### Release Notes Format ```markdown #### 1.2.0 January 15th 2025 #### - Added new feature X - Fixed bug in Y - Improved performance of Z #### 1.1.0 December 10th 2024 #### - Initial release with features A, B, C ``` ### Parsing Script (getReleaseNotes.ps1) ```powershell function Get-ReleaseNotes { param ( [Parameter(Mandatory=$true)] [string]$MarkdownFile ) $content = Get-Content -Path $MarkdownFile -Raw $sections = $content -split "####" $result = [PSCustomObject]@{ Version = $null Date = $null ReleaseNotes = $null } if ($sections.Count -ge 3) { $header = $sections[1].Trim() $releaseNotes = $sections[2].Trim() $headerParts = $header -split " ", 2 if ($headerParts.Count -eq 2) { $result.Version = $headerParts[0] $result.Date = $headerParts[1] } $result.ReleaseNotes = $releaseNotes } return $result } ``` ### Version Bump Script (bumpVersion.ps1) ```powershell function UpdateVersionAndReleaseNotes { param ( [Parameter(Mandatory=$true)] [PSCustomObject]$ReleaseNotesResult, [Parameter(Mandatory=$true)] [string]$XmlFilePath ) $xmlContent = New-Object XML $xmlContent.Load($XmlFilePath) # Update VersionPrefix $versionElement = $xmlContent.SelectSingleNode("//VersionPrefix") $versionElement.InnerText = $ReleaseNotesResult.Version # Update PackageReleaseNotes $notesElement = $xmlContent.SelectSingleNode("//PackageReleaseNotes") $notesElement.InnerText = $ReleaseNotesResult.ReleaseNotes $xmlContent.Save($XmlFilePath) } ``` ### Build Script (build.ps1) ```powershell # Load helper scripts . "$PSScriptRoot\scripts\getReleaseNotes.ps1" . "$PSScriptRoot\scripts\bumpVersion.ps1" # Parse release notes and update Directory.Build.props $releaseNotes = Get-ReleaseNotes -MarkdownFile (Join-Path -Path $PSScriptRoot -ChildPath "RELEASE_NOTES.md") UpdateVersionAndReleaseNotes -ReleaseNotesResult $releaseNotes -XmlFilePath (Join-Path -Path $PSScriptRoot -ChildPath "Directory.Build.props") Write-Output "Updated to version $($releaseNotes.Version)" ``` ### CI/CD Integration ```yaml # GitHub Actions example - 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 }} - name: Push to NuGet run: dotnet nuget push **/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json ``` --- ## NuGet.Config Configure NuGet sources and behavior: ```xml ``` **Key Settings:** - `` - Remove inherited/default sources for reproducible builds - `disableSourceControlIntegration` - Prevents TFS/Git integration issues --- ## Complete Project Structure ``` MySolution/ ├── .config/ │ └── dotnet-tools.json # Local .NET tools ├── .github/ │ └── workflows/ │ ├── pr-validation.yml # PR checks │ └── release.yml # NuGet publishing ├── scripts/ │ ├── getReleaseNotes.ps1 # Parse RELEASE_NOTES.md │ └── bumpVersion.ps1 # Update Directory.Build.props ├── src/ │ ├── MyApp/ │ │ └── MyApp.csproj │ └── MyApp.Core/ │ └── MyApp.Core.csproj ├── tests/ │ └── MyApp.Tests/ │ └── MyApp.Tests.csproj ├── Directory.Build.props # Centralized build config ├── Directory.Packages.props # Central package versions ├── MySolution.slnx # Modern solution file ├── global.json # SDK version pinning ├── NuGet.Config # Package source config ├── build.ps1 # Build orchestration ├── RELEASE_NOTES.md # Version history ├── README.md # Project documentation └── logo.png # Package icon ``` --- ## 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 (parsed by build) | | `build.ps1` | Build orchestration script | | `.config/dotnet-tools.json` | Local .NET tools |