.NET Projektstruktur und Build-Konfiguration
Wann dieses Skill verwendet werden sollte
Verwenden Sie dieses Skill, wenn:
- Sie eine neue .NET-Lösung mit modernen Best Practices einrichten
- Sie zentrale Build-Eigenschaften über mehrere Projekte hinweg konfigurieren möchten
- Sie zentrale Paketversionsverwaltung implementieren möchten
- Sie SourceLink für Debugging und NuGet-Pakete einrichten möchten
- Sie die Versionsverwaltung mit Release Notes automatisieren möchten
- Sie SDK-Versionen für konsistente Builds festlegen möchten
Verwandte Skills
dotnet-local-tools– Verwaltung lokaler .NET-Tools mit dotnet-tools.jsonmicrosoft-extensions-configuration– Konfigurationsvalidierungsmuster
Lösungsdateiformat (.slnx)
Das .slnx-Format ist das moderne XML-basierte Lösungsdateiformat, das mit .NET 9 eingeführt wurde. Es ersetzt das traditionelle .sln-Format.
Vorteile gegenüber traditionellem .sln
| Aspekt | .sln (Legacy) | .slnx (Modern) |
|---|---|---|
| Format | Benutzerdefiniertes Textformat | Standard XML |
| Lesbarkeit | GUIDs, kryptische Syntax | Sauber, menschenlesbar |
| Versionskontrolle | Schwer zu diffen/mergen | Leicht zu diffen/mergen |
| Bearbeitung | IDE erforderlich | Jeder Texteditor |
Versionsanforderungen
| Tool | Mindestversion |
|---|---|
| .NET SDK | 9.0.200 |
| Visual Studio | 17.13 |
| MSBuild | Visual Studio Build Tools 17.13 |
Hinweis: Ab .NET 10 erstellt dotnet new sln standardmäßig .slnx-Dateien. In .NET 9 müssen Sie explizit migrieren oder das Format angeben.
Beispiel einer .slnx-Datei
<Solution>
<Folder Name="/build/">
<File Path="Directory.Build.props" />
<File Path="Directory.Packages.props" />
<File Path="global.json" />
<File Path="NuGet.Config" />
<File Path="README.md" />
</Folder>
<Folder Name="/src/">
<Project Path="src/MyApp/MyApp.csproj" />
<Project Path="src/MyApp.Core/MyApp.Core.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/MyApp.Tests/MyApp.Tests.csproj" />
</Folder>
</Solution>
Migration von .sln zu .slnx
Verwenden Sie den Befehl dotnet sln migrate, um vorhandene Lösungen zu konvertieren:
# Eine bestimmte Lösungsdatei migrieren
dotnet sln MySolution.sln migrate
# Oder falls nur eine .sln im Verzeichnis existiert, einfach ausführen:
dotnet sln migrate
Wichtig: Behalten Sie nicht beide Dateien (.sln und .slnx) im selben Repository. Dies verursacht Probleme bei der automatischen Lösungsfindung und kann zu Synchronisationsproblemen führen. Löschen Sie nach der Migration die alte .sln-Datei.
Sie können auch in Visual Studio migrieren:
- Öffnen Sie die Lösung
- Wählen Sie die Lösung im Projektmappen-Explorer aus
- Gehen Sie zu Datei > Projektmappe speichern unter...
- Ändern Sie den Dateityp auf Xml-Projektmappendatei (*.slnx)
Erstellen einer neuen .slnx-Lösung
# .NET 10+: Erstellt standardmäßig .slnx
dotnet new sln --name MySolution
# .NET 9: Format explizit angeben
dotnet new sln --name MySolution --format slnx
# Projekte hinzufügen (funktioniert für beide Formate gleich)
dotnet sln add src/MyApp/MyApp.csproj
Empfehlung
Wenn Sie .NET 9.0.200 oder höher verwenden, migrieren Sie Ihre Lösungen zu .slnx. Die Vorteile sind erheblich:
- Deutlich weniger Merge-Konflikte (keine zufälligen GUID-Änderungen)
- Menschenlesbar und in jedem Texteditor bearbeitbar
- Konsistent mit dem modernen
.csproj-Format - Bessere Diff/Review-Erfahrung in Pull Requests
Directory.Build.props
Directory.Build.props bietet eine zentrale Build-Konfiguration, die für alle Projekte in einem Verzeichnisbaum gilt. Platzieren Sie sie im Lösungsstammverzeichnis.
Vollständiges Beispiel
<Project>
<!-- Metadaten -->
<PropertyGroup>
<Authors>Ihr Team</Authors>
<Company>Ihre Firma</Company>
<!-- Dynamisches Copyright-Jahr – aktualisiert sich automatisch -->
<Copyright>Copyright © 2020-$([System.DateTime]::Now.Year) Ihre Firma</Copyright>
<Product>Ihr Produkt</Product>
<PackageProjectUrl>https://github.com/yourorg/yourrepo</PackageProjectUrl>
<RepositoryUrl>https://github.com/yourorg/yourrepo</RepositoryUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageTags>your;tags;here</PackageTags>
</PropertyGroup>
<!-- C#-Spracheinstellungen -->
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>$(NoWarn);CS1591</NoWarn> <!-- Fehlende XML-Kommentare -->
</PropertyGroup>
<!-- Versionsverwaltung -->
<PropertyGroup>
<VersionPrefix>1.0.0</VersionPrefix>
<PackageReleaseNotes>Siehe RELEASE_NOTES.md</PackageReleaseNotes>
</PropertyGroup>
<!-- Zielframework-Definitionen (wiederverwendbare Eigenschaften) -->
<PropertyGroup>
<NetStandardLibVersion>netstandard2.0</NetStandardLibVersion>
<NetLibVersion>net8.0</NetLibVersion>
<NetTestVersion>net9.0</NetTestVersion>
</PropertyGroup>
<!-- SourceLink-Konfiguration -->
<PropertyGroup>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
</ItemGroup>
<!-- NuGet-Paket-Assets -->
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)logo.png" Pack="true" PackagePath="\" />
<None Include="$(MSBuildThisFileDirectory)README.md" Pack="true" PackagePath="\" />
</ItemGroup>
<PropertyGroup>
<PackageIcon>logo.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<!-- Globale Using-Anweisungen -->
<ItemGroup>
<Using Include="System.Collections.Immutable" />
</ItemGroup>
</Project>
Wichtige Muster
Dynamisches Copyright-Jahr
<Copyright>Copyright © 2020-$([System.DateTime]::Now.Year) Ihre Firma</Copyright>
Verwendet MSBuild-Eigenschaftenfunktionen, um das aktuelle Jahr zur Build-Zeit einzufügen. Keine manuellen Aktualisierungen erforderlich.
Wiederverwendbare Zielframework-Eigenschaften
Definieren Sie Zielframeworks einmal, referenzieren Sie sie überall:
<!-- In Directory.Build.props -->
<PropertyGroup>
<NetLibVersion>net8.0</NetLibVersion>
<NetTestVersion>net9.0</NetTestVersion>
</PropertyGroup>
<!-- In MyApp.csproj -->
<PropertyGroup>
<TargetFramework>$(NetLibVersion)</TargetFramework>
</PropertyGroup>
<!-- In MyApp.Tests.csproj -->
<PropertyGroup>
<TargetFramework>$(NetTestVersion)</TargetFramework>
</PropertyGroup>
SourceLink für NuGet-Pakete
SourceLink ermöglicht Schritt-für-Schritt-Debugging von NuGet-Paketen:
<PropertyGroup>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup>
<!-- Wählen Sie den richtigen Anbieter für Ihre Versionskontrolle -->
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
<!-- Oder: Microsoft.SourceLink.AzureRepos.Git -->
<!-- Oder: Microsoft.SourceLink.GitLab -->
<!-- Oder: Microsoft.SourceLink.Bitbucket.Git -->
</ItemGroup>
Directory.Packages.props – Zentrale Paketverwaltung
Die zentrale Paketverwaltung (CPM) bietet eine einzige Quelle der Wahrheit für alle NuGet-Paketversionen.
Einrichtung
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<!-- Versionsvariablen für zusammengehörige Pakete definieren -->
<PropertyGroup>
<AkkaVersion>1.5.35</AkkaVersion>
<AspireVersion>9.1.0</AspireVersion>
</PropertyGroup>
<!-- Anwendungsabhängigkeiten -->
<ItemGroup Label="App Dependencies">
<PackageVersion Include="Akka" Version="$(AkkaVersion)" />
<PackageVersion Include="Akka.Cluster" Version="$(AkkaVersion)" />
<PackageVersion Include="Akka.Persistence" Version="$(AkkaVersion)" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
</ItemGroup>
<!-- Build-/Tooling-Abhängigkeiten -->
<ItemGroup Label="Build Dependencies">
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
</ItemGroup>
<!-- Testabhängigkeiten -->
<ItemGroup Label="Test Dependencies">
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.1" />
<PackageVersion Include="FluentAssertions" Version="7.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.3" />
</ItemGroup>
</Project>
Pakete konsumieren (keine Version erforderlich)
<!-- In MyApp.csproj -->
<ItemGroup>
<PackageReference Include="Akka" />
<PackageReference Include="Akka.Cluster" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>
<!-- In MyApp.Tests.csproj -->
<ItemGroup>
<PackageReference Include="xunit" />
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
</ItemGroup>
Vorteile
- Einzelne Quelle der Wahrheit – Alle Versionen in einer Datei
- Kein Versionsdrift – Alle Projekte verwenden dieselben Versionen
- Einfache Aktualisierungen – Einmal ändern, überall wirksam
- Gruppierte Pakete – Versionsvariablen für zusammengehörige Pakete (z. B. alle Akka-Pakete)
global.json – SDK-Version festlegen
Legen Sie die .NET SDK-Version für konsistente Builds in allen Umgebungen fest.
{
"sdk": {
"version": "9.0.200",
"rollForward": "latestFeature"
}
}
Roll-Forward-Richtlinien
| Richtlinie | Verhalten |
|---|---|
disable | Exakte Version erforderlich |
patch | Gleiche Haupt-/Nebenversion, neuestes Patch |
feature | Gleiche Hauptversion, neueste Nebenversion/Patch |
latestFeature | Gleiche Hauptversion, neuestes Feature-Band |
minor | Gleiche Hauptversion, neueste Nebenversion |
latestMinor | Gleiche Hauptversion, neueste Nebenversion |
major | Neuestes SDK (nicht empfohlen) |
Empfohlen: latestFeature – Erlaubt Patch-Updates innerhalb desselben Feature-Bands.
Versionsverwaltung mit RELEASE_NOTES.md
Format der Release Notes
#### 1.2.0 15. Januar 2025 ####
- Neue Funktion X hinzugefügt
- Fehler in Y behoben
- Leistung von Z verbessert
#### 1.1.0 10. Dezember 2024 ####
- Erste Veröffentlichung mit Funktionen A, B, C
Parsing-Skript (getReleaseNotes.ps1)
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
}
Versionserhöhungsskript (bumpVersion.ps1)
function UpdateVersionAndReleaseNotes {
param (
[Parameter(Mandatory=$true)]
[PSCustomObject]$ReleaseNotesResult,
[Parameter(Mandatory=$true)]
[string]$XmlFilePath
)
$xmlContent = New-Object XML
$xmlContent.Load($XmlFilePath)
# VersionPrefix aktualisieren
$versionElement = $xmlContent.SelectSingleNode("//VersionPrefix")
$versionElement.InnerText = $ReleaseNotesResult.Version
# PackageReleaseNotes aktualisieren
$notesElement = $xmlContent.SelectSingleNode("//PackageReleaseNotes")
$notesElement.InnerText = $ReleaseNotesResult.ReleaseNotes
$xmlContent.Save($XmlFilePath)
}
Build-Skript (build.ps1)
# Hilfsskripte laden
. "$PSScriptRoot\scripts\getReleaseNotes.ps1"
. "$PSScriptRoot\scripts\bumpVersion.ps1"
# Release Notes parsen und Directory.Build.props aktualisieren
$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 "Auf Version $($releaseNotes.Version) aktualisiert"
CI/CD-Integration
# GitHub Actions Beispiel
- name: Version aus Release Notes aktualisieren
shell: pwsh
run: ./build.ps1
- name: Build
run: dotnet build -c Release
- name: Mit Tag-Version packen
run: dotnet pack -c Release /p:PackageVersion=${{ github.ref_name }}
- name: Nach NuGet pushen
run: dotnet nuget push **/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
NuGet.Config
NuGet-Quellen und Verhalten konfigurieren:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<solution>
<add key="disableSourceControlIntegration" value="true" />
</solution>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<!-- Private Feeds bei Bedarf hinzufügen -->
<!-- <add key="MyCompany" value="https://pkgs.dev.azure.com/myorg/_packaging/myfeed/nuget/v3/index.json" /> -->
</packageSources>
</configuration>
Wichtige Einstellungen:
<clear />– Entfernt geerbte/Standardquellen für reproduzierbare BuildsdisableSourceControlIntegration– Verhindert TFS/Git-Integrationsprobleme
Vollständige Projektstruktur
MySolution/
├── .config/
│ └── dotnet-tools.json # Lokale .NET-Tools
├── .github/
│ └── workflows/
│ ├── pr-validation.yml # PR-Überprüfungen
│ └── release.yml # NuGet-Veröffentlichung
├── scripts/
│ ├── getReleaseNotes.ps1 # RELEASE_NOTES.md parsen
│ └── bumpVersion.ps1 # Directory.Build.props aktualisieren
├── src/
│ ├── MyApp/
│ │ └── MyApp.csproj
│ └── MyApp.Core/
│ └── MyApp.Core.csproj
├── tests/
│ └── MyApp.Tests/
│ └── MyApp.Tests.csproj
├── Directory.Build.props # Zentrale Build-Konfiguration
├── Directory.Packages.props # Zentrale Paketverwaltung
├── global.json # SDK-Version festlegen
├── MySolution.slnx # Modernes Lösungsdateiformat
├── NuGet.Config # NuGet-Konfiguration
├── README.md # Projekt-Dokumentation
├── RELEASE_NOTES.md # Release Notes
└── logo.png # Paket-Logo
Nächste Schritte
Nachdem Sie die Grundstruktur eingerichtet haben:
- Projektabhängigkeiten konfigurieren – Verweise zwischen Projekten hinzufügen, z. B.
MyApp > MyApp.Core - CI/CD-Pipelines einrichten – Build- und Test-Workflows mit GitHub Actions, Azure DevOps oder anderen
- NuGet-Pakete hinzufügen – Abhängigkeiten in
Directory.Packages.propsdefinieren - Versionsstrategie einrichten – Release Notes pflegen und automatisierte Versionierung mittels
build.ps1 - Dokumentation schreiben – README.md und weitere Dokumentation mit Projektbeschreibung und Architektur