Aller au contenu

Centraliser les versions des packages NuGet avec Central Package Management

Introduction

Dans une solution .NET composée de plusieurs projets, il est fréquent que plusieurs d’entre eux utilisent les mêmes packages NuGet. Lorsque les versions de ces packages sont directement déclarées dans chaque fichier projet (.csproj), leur mise à jour peut nécessiter de modifier plusieurs fichiers et il devient possible que différents projets utilisent différentes versions d’un même package. Pour éviter cette situation, les informations relatives aux packages NuGet peuvent être centralisées dans un fichier qui s'applique à tous les projets de la solution.

Deux fichiers peuvent alors attirer notre attention : Directory.Build.props et Directory.Packages.props. Bien qu’ils reposent tous les deux sur MSBuild et puissent être placés à la racine d’une arborescence de projets, ils ne répondent pas au même objectif. Le fichier Directory.Build.props permet de définir des propriétés et des paramètres de compilation communs à plusieurs projets, tandis que le fichier Directory.Packages.props est spécifiquement prévu pour centraliser la gestion des versions des packages NuGet.

Dans cet article, nous verrons le fonctionnement de ces deux fichiers et pourquoi le fichier Directory.Packages.props, utilisé avec Central Package Management (CPM), constitue la solution à privilégier pour gérer les versions des packages NuGet.

Problématique

Imaginons une solution contenant plusieurs projets :

Arborescence projets

Plusieurs de ces projets utilisent par exemple les packages NuGet suivants :

  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.SqlServer
  • FluentValidation
  • Serilog

Une approche classique consiste à préciser directement la version du package dans chaque fichier projet :

<ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore"
                      Version="10.0.0" />

    <PackageReference Include="FluentValidation"
                      Version="12.0.0" />
</ItemGroup>

Si plusieurs projets utilisent ces packages, les mêmes versions sont alors répétées dans plusieurs fichiers. Et lors d'une mise à jour, il faut vérifier chaque projet afin de s'assurer qu'ils utilisent la même version attendue. L'objectif est donc de pouvoir déclarer les packages utilisés dans les projets tout en centralisant leurs versions dans un emplacement unique.

Le fichier Directory.Build.props

MSBuild permet de créer un fichier Directory.Build.props afin de définir des propriétés communes aux projets situés dans un répertoire et ses sous-répertoires. Voici un exemple :

<Project>

    <PropertyGroup>
        <TargetFramework>net10.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
        <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    </PropertyGroup>

</Project>

Ces propriétés peuvent ainsi être partagées par plusieurs projets sans être répétées dans chaque fichier projet. Ce fichier est donc particulièrement adapté à la centralisation des paramètres MSBuild relatifs à la compilation et à la configuration des projets. Il est aussi possible d'utiliser des propriétés définies dans ce fichier pour centraliser les numéros de version des packages :

<Project>

    <PropertyGroup>
        <EntityFrameworkCoreVersion>10.0.0</EntityFrameworkCoreVersion>
        <FluentValidationVersion>12.0.0</FluentValidationVersion>
    </PropertyGroup>

</Project>

Puis de les utiliser dans les fichiers projet :

<ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore"
                      Version="$(EntityFrameworkCoreVersion)" />

    <PackageReference Include="FluentValidation"
                      Version="$(FluentValidationVersion)" />
</ItemGroup>

Cette approche permet effectivement d'éviter de répéter les numéros de version. Cependant, le fichier Directory.Build.props n'a pas été conçu spécifiquement pour gérer les versions des packages NuGet. NuGet propose une solution dédiée répondant à cette problématique : Central Package Management.

Présentation de Central Package Management (CPM)

Central Package Management (CPM) permet de gérer les versions des packages NuGet utilisées par plusieurs projets depuis un fichier centralisé. Il est disponible depuis NuGet 6.2, fourni avec le SDK .NET 6.0.300 et Visual Studio 2022 17.2. Il repose sur le fichier nommé Directory.Packages.props qui permet de séparer les responsabilités :

  • Chaque fichier projet indique les packages qu'il utilise.
  • Le fichier Directory.Packages.props indique la version de chaque package qui doit être utilisée.

Cette séparation simplifie la gestion des dépendances dans les solutions comportant plusieurs projets.

Créer le fichier Directory.Packages.props

Le fichier Directory.Packages.props est généralement placé à la racine de la solution ou du dépôt. Voici un exemple de contenu :

<Project>

    <PropertyGroup>
        <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
    </PropertyGroup>

    <ItemGroup>
        <PackageVersion Include="Microsoft.EntityFrameworkCore"
                        Version="10.0.0" />

        <PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer"
                        Version="10.0.0" />

        <PackageVersion Include="FluentValidation"
                        Version="12.0.0" />

        <PackageVersion Include="Serilog"
                        Version="4.3.0" />
    </ItemGroup>

</Project>

La propriété ManagePackageVersionsCentrally permet d'activer la gestion centralisée des versions. Chaque package et sa version sont ensuite déclarés avec un élément PackageVersion.

Déclarer les packages dans les projets

Une fois Central Package Management activé, les fichiers projet continuent de déclarer les packages dont ils ont besoin. En revanche, leur version n'est plus précisée :

<ItemGroup>

    <PackageReference Include="Microsoft.EntityFrameworkCore" />

    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />

    <PackageReference Include="FluentValidation" />

</ItemGroup>

La version de chacun de ces packages est alors définie dans le fichier Directory.Packages.props. Nous obtenons donc une séparation des responsabilités :

Par exemple, le projet Infrastructure utilise Entity Framework Core :

<ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
</ItemGroup>

tandis que le projet Application utilise uniquement FluentValidation :

<ItemGroup>
    <PackageReference Include="FluentValidation" />
</ItemGroup>

Les versions restent centralisées dans un même fichier.

Mise à jour d'un package

L'un des principaux avantages apparaît lorsqu'il faut mettre à jour une dépendance. Supposons que plusieurs projets utilisent ce package NuGet :

<PackageVersion Include="Microsoft.EntityFrameworkCore" 
                Version="10.0.0" />

Pour passer à la version 10.0.1, une seule modification est nécessaire :

<PackageVersion Include="Microsoft.EntityFrameworkCore"
                Version="10.0.1" />

Tous les projets utilisant ce package bénéficieront alors de cette nouvelle version lors de la restauration NuGet.

Utiliser une version différente d'un package dans un projet

Central Package Management permet à un projet d'utiliser une version différente de celle définie dans le fichier Directory.Packages.props. Pour cela, l'attribut VersionOverride peut être utilisé :

<PackageVersion Include="Microsoft.EntityFrameworkCore"
                VersionOverride="10.0.0" />

Le projet utilisera donc la version 10.0.0, tandis que les autres projets continueront d'utiliser la version définie dans le fichier Directory.Packages.props. Cette possibilité peut être utile pour répondre à une contrainte particulière ou effectuer progressivement la migration d'un package. Son utilisation doit toutefois rester exceptionnelle afin de conserver la centralisation des versions.

Interdire la surcharge des versions

Il est également possible d'interdire aux projets de surcharger les versions centralisées en valorisant la propriété CentralPackageVersionOverrideEnabled à false :

<PropertyGroup>
    <CentralPackageVersionOverrideEnabled>
        false
    </CentralPackageVersionOverrideEnabled>
</PropertyGroup>

L'utilisation de l'attribut VersionOverride dans un fichier projet provoquera alors une erreur lors de la restauration des packages NuGet. Ce paramètre permet d'imposer les versions définies dans le fichier Directory.Packages.props.