In this blog post I’d like to show you a very simple technique for ensuring all your Nuget packages across all the projects in a solution have the same version. Such package management improvement introduced recently into the OmniSharp code by one and only Dustin Campbell, and I’ve also been using it in some of my projects.

If your solution is built around .NET SDK csproj projects, which were introduced in Visual Studio 2017, hopefully this blog post will be useful to you.

The Problem

It is inevitable in the course of larger application development, that – despite our best efforts – the versions of packages used throughout the solution diverge from each other. This can lead to lots of annoying warnings, and sometimes even unexpected errors. Smart package managers like Paket have solved this problem long time ago, but with Nuget it’s traditionally been an annoying issue.

The Nuget GUI in Visual Studio helps a little bit, because it lets you consolidate packages solution-wide, but nevertheless, the process is manual and the individual versions are still stored in their respective project files, so they are – sooner or later – bound to diverge again.

The Solution

Since in the new SDK-based projects, Nuget packages are no longer defined in external packages.config files, but rather embedded into the csproj project file directly, there is no reason not to leverage some of the MsBuild extensibility aspects to solve the packaging issues.

The simple solution is to extract all of the package version definitions into a centralized MsBuild-properties file. Then, this file can be imported throughout your solution, into each of the projects, and instead of a having versions defined there directly, the versions from the central properties file can be used. This way they are guaranteed to be the same everywhere.

The example is shown below. Let’s consider the following properties file, placed somewhere at the root of your solution (for example, next to the solution file), and called Packages.props.

<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <MicrosoftAzureServiceBusVersion>1.0.0</MicrosoftAzureServiceBusVersion> <SerilogVersion>2.5.0</SerilogVersion> <SerilogExtensionsLoggingVersion>1.4.0</SerilogExtensionsLoggingVersion> <WebApiContribCoreVersion>1.2.0</WebApiContribCoreVersion> <MicrosoftAzureDocumentDBCoreVersion>1.3.2</MicrosoftAzureDocumentDBCoreVersion> </PropertyGroup> </Project> 1 2 3 4 5 6 7 8 9 10 <? xml version = "1.0" encoding = "utf-8" ?> < Project ToolsVersion = "14.0" xmlns = "http://schemas.microsoft.com/developer/msbuild/2003" > < PropertyGroup > < MicrosoftAzureServiceBusVersion > 1.0.0 < / MicrosoftAzureServiceBusVersion > < SerilogVersion > 2.5.0 < / SerilogVersion > < SerilogExtensionsLoggingVersion > 1.4.0 < / SerilogExtensionsLoggingVersion > < WebApiContribCoreVersion > 1.2.0 < / WebApiContribCoreVersion > < MicrosoftAzureDocumentDBCoreVersion > 1.3.2 < / MicrosoftAzureDocumentDBCoreVersion > < / PropertyGroup > < / Project >

These are simply MsBuild properties geared to act as package versions. Each property has a name that corresponds to the package it represents (stripped out of names).

Now in each of the project files, the Packages.props can be imported, and package versions accessed as regular MsBuild properties.

For example:

<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp1.1</TargetFramework> </PropertyGroup> <Import Project="..\..\Packages.props" /> <ItemGroup> <PackageReference Include="Microsoft.Azure.DocumentDB.Core" Version="$(MicrosoftAzureDocumentDBCoreVersion)" /> <PackageReference Include="Microsoft.Azure.ServiceBus" Version="$(MicrosoftAzureServiceBusVersion)" /> <PackageReference Include="Serilog" Version="$(SerilogVersion)" /> <PackageReference Include="Serilog.Extensions.Logging" Version="$(SerilogExtensionsLoggingVersion)" /> <PackageReference Include="WebApiContrib.Core" Version="$(WebApiContribCoreVersion)" /> </ItemGroup> </Project> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 < Project Sdk = "Microsoft.NET.Sdk.Web" > < PropertyGroup > < TargetFramework > netcoreapp1 . 1 < / TargetFramework > < / PropertyGroup > < Import Project = "..\..\Packages.props" / > < ItemGroup > < PackageReference Include = "Microsoft.Azure.DocumentDB.Core" Version = "$(MicrosoftAzureDocumentDBCoreVersion)" / > < PackageReference Include = "Microsoft.Azure.ServiceBus" Version = "$(MicrosoftAzureServiceBusVersion)" / > < PackageReference Include = "Serilog" Version = "$(SerilogVersion)" / > < PackageReference Include = "Serilog.Extensions.Logging" Version = "$(SerilogExtensionsLoggingVersion)" / > < PackageReference Include = "WebApiContrib.Core" Version = "$(WebApiContribCoreVersion)" / > < / ItemGroup > < / Project >

This way all your projects are pinned to the same versions of packages, which you manage in a central place.