Si vous avez travaillé un peu avec l'environnement .NET, vous avez certainement utilisé NuGet pour gérer vos dépendances externes. Seulement, vous vous êtes peut-être rendu compte que NuGet ne se comporte absolument pas comme les autres gestionnaires de paquets, et c'est voulu. Voici pourquoi NuGet fut un outil mal conçu dès le départ, mais comment il se rattrape enfin avec les nouvelles solutions .NET Core.

Pourquoi NuGet est-il "brisé"?

Quand je dis que NuGet est brisé, c'est que je trouve que son utilisation va à l'encontre de tout ce que j'ai vu se faire sur le marché. D'habitude, un gestion de paquet est un outil mis de l'avant par une plate-forme. Node.js a npm, Ruby a ses gems, et Java a Maven (entre autres). Mais qu'ont ces gestionnaires des paquets que NuGet n'a pas? La réponse: une approche cohérente et simple. Quand j'utilise npm et que je veux mettre-à-jour un paquet, j'ouvre mon fichier nommé project.json, et je change le numéro de version. Ensuite, je relance une installation, et le tour est joué (s'il n'y a pas de breaking changes).

Avez-vous essayé cela avec NuGet? Pour le plaisir, installez une ancienne version d'un paquet, disons JSON.NET en version 6. Ouvrez votre fichier package.config et changez le numéro de version. Détruisez votre dossier packages et lancez un Build. Voyez comment tout explose gracieusement. Voici la magie de NuGet.

Pourquoi est-ce que NuGet et .NET brisent dans un cas où la quasi-totalité des autres solutions parviennent à s'en tirer facilement?

Une leçon d'histoire

NuGet est un projet qui a débuté en 2010. À cette époque, Microsoft aimait bien inventer des solutions différentes, disons-le comme cela pour rester poli. Ils ont vu une opportunité d'améliorer la gestion des dépendances externes, mais on dirait qu'ils ne se sont pas commis au complet. À la place, nous avons eu droit à NuGet. La vision en arrière de l'outil était simple. Au lieu que les développeurs aient à parcourir le Web à la recherche des dll à référencer, les développeurs passeraient par un outil intégré qui serait en charge de télécharger ces paquets ainsi que leurs dépendances et de les restaurer pour éviter que celles-ci ne se retrouvent dans le Source Control. C'était quand même loin d'une mauvaise idée, mais il faut savoir que NuGet ne remplaçait pas du tout le processus de référencement des dépendances des projets. À la place, NuGet aurait son propre fichier et aurait la possibilité de modifier les csproj afin d'injecter les dépendances.

<ItemGroup>
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
      <HintPath>..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
      <Private>True</Private>
    </Reference>
    <Reference Include="SendGrid.SmtpApi, Version=1.3.1.0, Culture=neutral, PublicKeyToken=2ae73662c35d80e4, processorArchitecture=MSIL">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\packages\SendGrid.SmtpApi.1.3.1\lib\net40\SendGrid.SmtpApi.dll</HintPath>
    </Reference>
    <Reference Include="System" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="System.Net" />
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Web.Extensions" />
</ItemGroup>
Petite partie d'un fichier csproj presque illisible et incompréhensible de 97 lignes. C'est ce fichier qui sera utilisé lors de la compilation pour savoir où se trouvent les dépendances à inclure.
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" />
  <package id="SendGrid.SmtpApi" version="1.3.1" targetFramework="net45" />
</packages>
Fichier package.config répétant les informations déjà contenues dans le csproj. Ce fichier sera utilisé exclusivement par NuGet pour restaurer les paquets.

Voyez-vous pourquoi cette approche est horrible? Le fichier package.config n'est qu'une pâle copie des informations contenues dans le csproj, et pour que tout fonctionne, il faut que ces informations se complètent. NuGet sera en charge de télécharger la croquette et placer les dlls aux endroits spécifiés par les HintPath, et le compilateur prendra alors ces dlls lors de son processus de compilation. Si une seule virgule est un peu croche, le compilateur ne sera pas en mesure de référencer une des dépendances et le Build échouera. Ce dédoublement est absurde, car il mène à des scénarios farfelus: Une croquette (paquet NuGet) n'est en aucun cas obligé de refléter les versions des binaires qu'il contient, pas plus qu'elle n'est obligée de dépendre fortement des dépendances de ses binaires. On se retrouve donc avec deux systèmes de versionnement et deux systèmes de dépendances. C'est d'un ridicule incroyable comparé à tout ce qui se fait sur le marché! Il était temps que ça évolue, et c'est ce que Microsoft a finalement fait lors de sa réécriture de .NET Core.

La nouvelle approche

Je crois que Microsoft a réalisé l'absurdité de la chose. Pour que NuGet puisse bien fonctionner, il ne doit pas se contenter de télécharger et restaurer des croquettes: il doit devenir un composant intégré du processus de compilation. C'est l'approche qui a été prise en .NET Core, et c'est une bonne chose. En .NET Core, les fichiers csproj ont disparus pour laisser place à un fichier project.json. C'est ce fichier maintenant qui décrit un projet. En plus d'être en JSON, ce fichier contient bien moins d'informations qu'un vieux csproj, ce qui le rend nettement plus compréhensible: L'équipe de développement a opté pour une approche opt-out plutôt que le opt-in auquel on était habitué avec les csproj. Maintenant, on ne décrit donc plus que les fichiers et dossiers que l'on souhaite exclure, ce qui réduit grandement la taille du fichier.

{
  "version": "1.0.0",
  "webroot": "wwwroot",
  "exclude": [
    "wwwroot"
  ],
  "dependencies": {
    "Kestrel": "1.0.0-beta4",
    "Microsoft.AspNet.Diagnostics": "1.0.0-beta4",
    "Microsoft.AspNet.Hosting": "1.0.0-beta4",
    "Microsoft.AspNet.Server.IIS": "1.0.0-beta4",
    "Microsoft.AspNet.Server.WebListener": "1.0.0-beta4",
    "Microsoft.AspNet.StaticFiles": "1.0.0-beta4"
  },
  "commands": {
      "web": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5000",
  },
  "frameworks": {
    "dnx451": { },
    "dnxcore50": { }
  }
}
Voici un exemple de fichier project.json.

Voyez-vous le noeud dependencies? C'est là qu'entre en compte NuGet. Tout d'abord, le projet tentera de résoudre une dépendance à l'intérieur de la solution. S'il n'y arrive pas, et que l'on utilise le framework dnx451, le GAC sera utilisé (le GAC n'existe plus avec .NET Core). Puis, si toujours rien n'est trouvé, NuGet sera utilisé afin de résoudre le paquet. Si même NuGet n'y arrive pas, cette dépendance est considérée comme étant fautive et doit être réparée. Voilà un système qui se comporte comme il se doit et qui fonctionne bien mieux! Maintenant, quand vous voulez changer de version pour un paquet, vous n'avez qu'à modifier le fichier project.json, recompiler, et le tour est joué! Cerise sur le gâteau, le tout est gérable à même l'outil en ligne de commande dnu. Voilà qui devrait faire le bonheur de nos amis devops qui en bavaient avec l'ancien système de croquettes qui était bien intégré au sein de l'IDE, mais qui peinait en terme de ligne de commande (il fallait bien souvent inclure le NuGet.exe dans le Source Control).

J'espère que vous aimerez les améliorations du nouveau .NET. La boîte à outils semble nettement plus complète que ce à quoi on avait droit par le passé. Vous connaissez le dicton: Quand tout ce qu'on a, c'est un marteau, tout ressemble à des clous. Et bien ce ne sera plus le cas en .NET.