web-dev-qa-db-fra.com

Comment résoudre l'enfer de la dépendance NuGet

Je développe une bibliothèque avec des fonctionnalités nommées CompanyName.SDK qui doit être intégré au projet d'entreprise CompanyName.SomeSolution

CompanyName.SDK.dll doit être déployé via le package NuGet. Et CompanyName.SDK le package dépend des packages NuGet tiers. Pour un bon exemple, prenons Unity . La dépendance actuelle dépend de v3.5.1405-prerelease de Unity.

CompanyName.SomeSolution.Project1 dépend de Unityv2.1.505.2. CompanyName.SomeSolution.Project2 dépend de Unityv3.0.1304.1.

En intégrant CompanyName.SDK dans cette solution ajoute une dépendance à Unityv3.5.1405-prerelease. Prenons ça CompanyName.SomeSolution a un projet de sortie exécutable CompanyName.SomeSolution.Application cela dépend de deux ci-dessus et de CompanyName.SDK

Et ici commencent les problèmes. Tous les assemblages Unity ont des noms égaux dans tous les packages sans spécificateur de version. Et dans le dossier cible, il n'y aura qu'une seule version des assemblages Unity: v3.5.1405-prerelease via bindingRedirect dans app.config.

Comment coder en Project1, Project2 et SDK utilisent les versions exactement nécessaires des packages dépendants avec lesquels ils ont été codés, compilés et testés?

NOTE1:Unity n'est qu'un exemple, la situation réelle est 10 fois pire avec des modules tiers dépendants d'un autre module tiers qui à son tour a 3-4 versions simultanément.

NOTE2: Je ne peux pas mettre à niveau tous les packages vers leurs dernières versions car il existe des packages qui ont une dépendance non sur la dernière version d'un autre package.

NOTE3: Supposons que les packages dépendants ont des changements de rupture entre les versions. C'est le vrai problème pour lequel je pose cette question.

NOTE4: Je connais la question sur les conflits entre les différentes versions du même assembly dépendant mais les réponses ne résolvent pas la racine d'un problème - ils le cachent juste.

NOTE5: Où diable est la solution promise du problème "DLL Hell"? Il réapparaît juste d'une autre position.

NOTE6: Si vous pensez que l'utilisation de GAC est en quelque sorte une option, écrivez un guide étape par étape s'il vous plaît ou donnez-moi un lien.

46
v.karbovnichy

Unity package n'est pas un bon exemple car vous ne devez l'utiliser qu'à un seul endroit appelé Composition Root . Et Composition Root doit être aussi proche que possible du point d'entrée de l'application. Dans votre exemple, c'est CompanyName.SomeSolution.Application

En dehors de cela, où je travaille maintenant, exactement le même problème apparaît. Et ce que je vois, le problème est souvent introduit par des préoccupations transversales comme l'exploitation forestière. La solution que vous pouvez appliquer consiste à convertir vos dépendances tierces en dépendances propriétaires. Vous pouvez le faire en introduisant des abstractions pour ces concepts. En fait, cela a d'autres avantages comme:

  • plus code maintenable
  • meilleure testabilité
  • se débarrasser des dépendances indésirables (chaque client de CompanyName.SDK a vraiment besoin de la dépendance Unity?)

Prenons donc un exemple imaginaire .NET Logging bibliothèque:

CompanyName.SDK.dll dépend de .NET Logging 3.0
CompanyName.SomeSolution.Project1 dépend de .NET Logging 2.0
CompanyName.SomeSolution.Project2 dépend de .NET Logging 1.0

Il y a des changements de rupture entre les versions de .NET Logging.

Vous pouvez créer votre propre dépendance propriétaire en introduisant l'interface ILogger:

public interface ILogger
{
    void LogWarning();
    void LogError();
    void LogInfo();
} 

CompanyName.SomeSolution.Project1 et CompanyName.SomeSolution.Project2 devrait utiliser l'interface ILogger. Ils dépendent de la dépendance interne de l’interface ILogger. Maintenant, vous gardez cela .NET Logging bibliothèque derrière un seul endroit et il est facile d'effectuer une mise à jour car vous devez le faire en un seul endroit. La rupture des modifications entre les versions n'est plus un problème, car une version de .NET Logging la bibliothèque est utilisée.

L'implémentation réelle de l'interface ILogger doit être dans un assembly différent et ne doit être que l'endroit où vous référencez .NET Logging bibliothèque. Dans CompanyName.SomeSolution.Application à l'endroit où vous composez votre application, vous devez maintenant mapper l'abstraction ILogger à une implémentation concrète.

Nous utilisons cette approche et nous utilisons également NuGet pour distribuer nos abstractions et nos implémentations. Malheureusement, des problèmes de versions peuvent apparaître avec vos propres packages. Pour éviter que des problèmes s'appliquent Version sémantique dans les packages que vous déployez via NuGet pour votre entreprise. Si quelque chose change dans votre base de code qui est distribué via NuGet, vous devez changer dans tous les packages qui sont distribués via NuGet. Par exemple, nous avons dans notre serveur local NuGet:

  • DomainModel
  • Services.Implementation.SomeFancyMessagingLibrary (qui fait référence à DomainModel et SomeFancyMessagingLibrary)
  • et plus...

La version entre ces packages est synchronisée, si la version est modifiée dans DomainModel, la même version est dans Services.Implementation.SomeFancyMessagingLibrary. Si nos applications nécessitent une mise à jour de nos packages internes, toutes les dépendances sont mises à jour vers la même version.

14
Arkadiusz K

Vous pouvez travailler au niveau de l'assemblage post-compilation pour résoudre ce problème avec ...

Option 1

Vous pouvez essayer de fusionner les assemblys avec ILMerge

ilmerge/target: winexe /out:SelfContainedProgram.exe Program.exe ClassLibrary1.dll ClassLibrary2.dll

Le résultat sera un assemblage qui est la somme de votre projet et de ses dépendances requises. Cela présente certains inconvénients, tels que la perte de la prise en charge mono et la perte des identités d'assembly (nom, version, culture, etc.), c'est donc préférable lorsque tous les assemblys à fusionner sont créés par vous.

Alors voici ...

Option 2

Vous pouvez à la place incorporer les dépendances en tant que ressources dans vos projets comme décrit dans cet article . Voici la partie pertinente (l'accent est mis sur moi):

Au moment de l'exécution, le CLR ne pourra pas trouver les assemblages dépendants DLL, ce qui est un problème. Pour résoudre ce problème, lors de l'initialisation de votre application, s'inscrire une méthode de rappel avec l'événement ResolveAssembly d'AppDomain. Le code devrait ressembler à ceci:

AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => {

   String resourceName = "AssemblyLoadingAndReflection." +

      new AssemblyName(args.Name).Name + ".dll";

   using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) {

      Byte[] assemblyData = new Byte[stream.Length];

      stream.Read(assemblyData, 0, assemblyData.Length);

      return Assembly.Load(assemblyData);

   }

};

Maintenant, la première fois qu'un thread appelle une méthode qui référence un type dans un fichier DLL DLL, l'événement AssemblyResolve dépendant) sera levé et le code de rappel ci-dessus trouvera la ressource DLL DLL désirée et la chargera en appelant une surcharge de la méthode Load de Assembly qui prend un octet [] comme argument.

Je pense que c'est l'option que j'utiliserais si j'étais à votre place, sacrifiant un temps de démarrage initial.

Mise à jour

Jetez un oeil ici . Vous pouvez également essayer d'utiliser ces <probing> balises dans le fichier app.config de chaque projet pour définir un sous-dossier personnalisé à rechercher lorsque le CLR recherche des assemblys.

12
beppe9000