.NET: Global Project Properties

Digitale Sternschnuppen
Willkommen zu den digitalen Sternschnuppen, deiner neuen Quelle für kompakte und wertvolle Informationen rund um den Software-Entwicklungsprozess! In dieser Artikelreihe präsentieren wir dir Best Practices, innovative Tools und Frameworks sowie Lessons Learned aus der Praxis. Jeder Artikel ist so gestaltet, dass du in maximal 5 Minuten ein Konzept nähergebracht bekommst – perfekt für die kurze Pause zwischendurch oder als inspirierende Lektüre.
Freue dich auf Themen wie:
- Clean Code: Grundlagen und fortgeschrittene Techniken für sauberen und wartbaren Code.
- Automatisiertes Testen: Tools und Strategien, um die Qualität deines Codes sicherzustellen.
- Clean Architecture: Prinzipien und Muster für eine robuste Softwarearchitektur.
- Documentation as Code: Effiziente Dokumentation direkt im Code.
- Infrastructure as Code: Automatisierung und Verwaltung deiner Infrastruktur.
- CI/CD und DevOps: Best Practices und Tools für kontinuierliche Integration und Bereitstellung mit Azure und Azure DevOps.
- Azure Cloud: Nutze die Vorteile der Cloud-Entwicklung mit Azure für skalierbare und effiziente Anwendungen.
Es geht bei den Artikeln nicht darum, die eine richtige Lösung zu präsentieren, sondern zum Nachdenken anzuregen und verschiedene Perspektiven zu beleuchten.
Bei vielen Themen und Ideen gibt es durchaus auch Alternativen und Meinungen, die wir wunderbar auf LinkedIn diskutieren können. Schaut gerne bei uns vorbei.
.NET: Global Project Properties
Die Verwaltung und Abstimmung von Eigenschaften über mehrere .NET-Projekte hinweg kann oft mühsam sein. Insbesondere wenn es um die Einstellung der verwendeten .NET-Version geht, oder welche Analyzer aktiv sein sollen, war das bisher eher eine lästige Aufgabe. Die Directory.Build.props-Datei hilft dabei, diese Hürden zu überwinden, indem sie eine zentrale Konfiguration bereitstellt, die für alle Projekte im gleichen Verzeichnis oder rekursiv in Unterordnern gilt.
Einmal angelegt, ermöglicht diese Datei es dir, zentrale Eigenschaften wie die C#-Version oder die verwendete .NET-Version festzulegen. Das reduziert den Aufwand, da du nicht in jedem einzelnen Projekt nachsehen musst, ob alle Einstellungen übereinstimmen und diese auch an einer Stelle für alle Projekte ändern kannst. Dadurch kann eine einfache Klassenbibliothek mit minimaler Konfiguration wie folgt aussehen:
<Project Sdk="Microsoft.NET.Sdk"></Project>
Was braucht es dazu?
Globale Einstellungen für Konsistenz
Zuallererst definierst du das Target Framework über die Directory.Build.props-Datei. Das sorgt dafür, dass alle Projekte dieselbe .NET- oder .NET Standard-Version verwenden. Wenn eine neue .NET-Version veröffentlicht wird, kannst du sie zentral anpassen. Dies gilt auch für die C#-Version, wobei du auch vorgeben kannst, ob die neuesten C#-Language-Features genutzt werden sollen oder nicht.
Zusätzlich gibt es moderne Features wie Nullable Reference Types und Implicit Usings, die nicht fehlen sollten. Diese tragen dazu bei, Null Reference Exceptions zu vermeiden und überflüssige Using-Statements im Code zu reduzieren. Mit folgenden Einstellung stellst du sicher, dass du den neuesten Stand der Technik nutzt.
<Project>
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
Wichtige Analyzer und Code Styles
Darüber hinaus kannst du auch Analyzer und Code Styles zentral konfigurieren. Das ermöglicht es, dass alle neuen Projekte direkt den festgelegten Richtlinien folgen. Mit folgenden Einstellungen stellst du sicher, dass die Analyzer-Standards eingehalten werden, ohne dass individuelle Projekte unterschiedlich agieren.
<PropertyGroup>
<AnalysisMode>Recommended</AnalysisMode>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
Die Standard .NET-Analyzer haben nicht alle Regeln out-of-the-box aktiviert. AnalysisMode sorgt dafür, dass alle empfohlenen Analyzer aktiviert sind.
EnforceCodeStyleInBuild sorgt dafür, dass zusätzliche Code Style-Regeln beim Build-Prozess angewendet werden.
Je nach Konfiguration, wie Debug oder Release, können unterschiedliche Regeln gelten. Beispielsweise kannst du im Release-Modus striktere Regeln anwenden, während du im Debug-Modus die Analyse etwas lockerer handhabst.
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<!-- Enforce code styling when building in Release mode so devs are not bothered during development in Debug mode -->
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
</PropertyGroup>
TreatWarningsAsErrors und CodeAnalysisTreatWarningsAsErrors sorgen dafür, dass Warnungen als Fehler behandelt werden, um sicherzustellen, dass der Code den definierten Standards entspricht und nicht einfach als Warnung ignoriert werden.
Für spezifische Projekte
Falls du unterschiedliche Einstellungen für verschiedene Projektarten benötigst, kannst du auch in Unterordnern neue Directory.Build.props-Dateien anlegen, die die übergeordneten Konfigurationen überschreiben. Dies sorgt für Flexibilität, wo unterschiedliche Projekte unterschiedliche Anforderungen haben können.
Auch kann es hilfreich sein, Projekttypen anhand von einheitlichen Namenskonventionen zu gruppieren. So kannst du z.B. Testprojekte anhand eines Suffix im Projektnamen erkennen und noch mal spezielle Einstellungen dafür setzen, wie die automatische Verwendung des Test Frameworks:
<PropertyGroup>
<IsTestProject>$(MSBuildProjectName.EndsWith('Tests'))</IsTestProject>
</PropertyGroup>
<ItemGroup Condition="$(IsTestProject)">
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit.v3" />
</ItemGroup>
Letzte Gedanken
Letztendlich ist die Entscheidung über die Verwendung von Directory.Build.props und die spezifischen Einstellungen eine gemeinsame Sache im Team. Eine gute Abstimmung sorgt dafür, dass alle Beteiligten nachvollziehen können, warum bestimmte Abhängigkeiten oder Eigenschaften festgelegt wurden, ohne dass alles manuell in jeder Projektdatei umständlich konfiguriert werden muss.
Für viele ist es vielleicht zu viel Magie, da man im Zweifel nicht mehr weiß, woher eine bestimmte Einstellung kommt. Hier sollte man konsequent darauf achten, nur eine einzelne Datei für die Konfiguration zu verwenden, statt zu verschachtelt die Einstellungen zu überschreiben, und diese auch entsprechend zu dokumentieren.
Ein finales Beispiel könnt ihr dem Open Space Planner entnehmen. Dieser ist unser Standard-Template für neue Projekte und zeigt, wie wir die Directory.Build.props-Datei in der Praxis einsetzen.
Was sind deine Erfahrungen mit der Directory.Build.props-Datei? Welche globalen Einstellungen hast du definiert oder warum nutzt du sie möglicherweise nicht? Schau gerne bei uns auf LinkedIn vorbei und diskutiere mit.