Simplifying .NET solution upgrades with Directory.Build.props and Directory.Packages.props
With .NET 8 almost released it is time to make upgrading easier. In this blog I’ll explain about Directory.Build.props
and Directory.Packages.props
and how these can make upgrading easier.
Most solutions consist of multiple projects. Even if all of your production code is in a single project chances are that your tests are in a different project. The separation that projects bring can be great, but sometimes managing dependencies can be a hassle.
The challenge with multiple csproj files
In many cases a solutions consists of multiple projects like depicted below.
└── solution/
├── src/
│ ├── Main
│ └── TheOtherProject
└── tests/
├── IntegrationTests
└── UnitTests
A typical csproj file contains a target framework, some project properties and some package references.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<RootNamespace>Main</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="3.0.1" />
<PackageReference Include="Serilog.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="7.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TheOtherProject.csproj" />
</ItemGroup>
</Project>
Each project defines it’s own dependencies and the versions of those dependencies. What happens when TheOtherProject
also uses Serilog but it is still on version 2.12.0? Most likely you’ll want all of those versions to be the same. Even with a good IDE like Rider and Visual Studio this can be a bit of a hassle.
A very similar problem migh arise when selecting the target framework. Chances are that you will have keep all of your projects within a solution aligned.
Creating clean and consistent project files
With .NET 6 Directory.Build.props
and Directory.Packages.props
were introduced. The names almost speak for themselves. These files contain directory wide build and packages properties. These files can be created in the top level of your solution folder. Typically placed where the sln file is stored.
Things like TargetFramework
and ImplicitUSings
can be set in the Directory.Build.props
as below.
<Project>
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>
Directory.Packages.props
is straight forward as well:
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Serilog" Version="3.0.1" />
<PackageVersion Include="Serilog.Extensions.Logging" Version="7.0.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="7.0.1" />
<PackageVersion Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
</ItemGroup>
</Project>
With these 2 files created Main.csproj
can be cleaned up:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<RootNamespace>Main</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" />
<PackageReference Include="Serilog.Extensions.Logging" />
<PackageReference Include="Serilog.Settings.Configuration" />
<PackageReference Include="Serilog.Sinks.Console" />
<PackageReference Include="Serilog.Sinks.File" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TheOtherProject.csproj" />
</ItemGroup>
</Project>
As can be seen the project file is now clear from any specific package or framework versions. These now depend on what is in the solution wide properties. That will aso make any package updates and framework updates consistent, by definition.
Installing packages
Support for these property files is fully embedded in the dotnet ecosystem. For example, when running dotnet add package Serilog
a terminal under solution/src/TheOtherProject/
.
Only “<PackageReference Include="Serilog" />
” is added to TheOtherProject.csproj
. Notice hte absence of a version number because it relies on what is in Directory.Packages.props
already.
The same goes for added a packages that no other project hase reference yet. donet add
will update TheOtherProject.csproj
and in addition update Directory.Packages.props
with the selected version.
Conclusion
Use of Directory.Build.props
and Directory.Packages.props
make a solution just a bit easier toe maintain. This will make an upgrade from .NET 6 or 7 to .NET 8 less complicated. The best is that you can do this before .NET 8 is out and already enjoy better package versioning.
In my experience bot Rider and Visual Studio also deal with this solution wide properties well. But always check your commits as from now on a packages version in a csproj file is something to try and avoid.