MSBuild ne copie pas les références (fichiers DLL) si vous utilisez des dépendances de projet dans la solution

273

J'ai quatre projets dans ma solution Visual Studio (tout le monde cible .NET 3.5) - pour mon problème, seuls ces deux sont importants:

  1. MyBaseProject <- cette bibliothèque de classes fait référence à un fichier DLL tiers (elmah.dll)
  2. MyWebProject1 <- ce projet d'application Web a une référence à MyBaseProject

J'ai ajouté la référence elmah.dll à MyBaseProject dans Visual studio 2008 en cliquant sur "Ajouter une référence ..." → onglet "Parcourir" → en sélectionnant "elmah.dll".

Les propriétés de la référence Elmah sont les suivantes:

  • Alias ​​- global
  • Copier local - vrai
  • Culture -
  • Description - Modules et gestionnaires de journalisation des erreurs (ELMAH) pour ASP.NET
  • Type de fichier - Assemblage
  • Chemin - D: \ webs \ otherfolder \ _myPath \ __ tools \ elmah \ Elmah.dll
  • Résolu - Vrai
  • Version d'exécution - v2.0.50727
  • Version spécifiée - false
  • Nom fort - faux
  • Version - 1.0.11211.0

Dans MyWebProject1, j'ai ajouté la référence au projet MyBaseProject en: "Ajouter une référence ..." → onglet "Projets" → en sélectionnant "MyBaseProject". Les propriétés de cette référence sont les mêmes, à l'exception des membres suivants:

  • La description -
  • Chemin - D: \ webs \ CMS \ MyBaseProject \ bin \ Debug \ MyBaseProject.dll
  • Version - 1.0.0.0

Si j'exécute la génération dans Visual Studio, le fichier elmah.dll est copié dans le répertoire bin de mon MyWebProject1 , avec MyBaseProject.dll!

Cependant, si je nettoie et exécute MSBuild pour la solution (via D: \ webs \ CMS> C: \ WINDOWS \ Microsoft.NET \ Framework \ v3.5 \ MSBuild.exe / t: ReBuild / p: Configuration = Debug MyProject.sln ) le elmah.dll est manquant dans le répertoire bin de MyWebProject1 - bien que la construction elle-même ne contienne aucun avertissement ou erreur!

J'ai déjà vérifié que le .csproj de MyBaseProject contient l' élément privé avec la valeur "true" (qui devrait être un alias pour " copier local " dans Visual Studio):

<Reference Include="Elmah, Version=1.0.11211.0, Culture=neutral, processorArchitecture=MSIL">
  <SpecificVersion>False</SpecificVersion>
  <HintPath>..\mypath\__tools\elmah\Elmah.dll</HintPath>
    **<Private>true</Private>**
</Reference>

(La balise privée n'apparaissait pas par défaut dans le xml du .csproj, bien que Visual Studio ait dit "copier local" vrai. J'ai basculé "copier local" sur faux - enregistré - et le redéfinir sur vrai - enregistrer!)

Quel est le problème avec MSBuild? Comment obtenir la référence (elmah.dll) copiée dans le bac de MyWebProject1?

Je ne veux PAS ajouter une action de copie post-construction à la commande post-construction de chaque projet! (Imaginez que de nombreux projets dépendent de MyBaseProject!)

toebens
la source
11
J'aimerais avoir une réponse plus claire pour expliquer pourquoi cela se produit.
David Faivre
1
Jetez un œil à la réponse fournie ici
Anuroopa Shenoy
1
une solution finale avec un échantillon complet de code source qui fonctionne?
Kiquenet
2
Voir la réponse stackoverflow.com/a/21055664/21579 ci-dessous par @deadlydog. Excellente explication et résolu le problème pour moi ... la réponse la plus votée ci-dessous n'est pas correcte pour VS2012.
Jeff Widmer

Réponses:

153

Je ne sais pas pourquoi il est différent lors de la construction entre Visual Studio et MsBuild, mais voici ce que j'ai trouvé lorsque j'ai rencontré ce problème dans MsBuild et Visual Studio.

Explication

Pour un exemple de scénario, supposons que nous ayons le projet X, l'assembly A et l'assembly B. L'assembly A fait référence à l'assembly B, donc le projet X inclut une référence à la fois à A et à B. De plus, le projet X inclut du code qui fait référence à l'assembly A (par exemple A. SomeFunction ()). Maintenant, vous créez un nouveau projet Y qui fait référence au projet X.

Ainsi, la chaîne de dépendance ressemble à ceci: Y => X => A => B

Visual Studio / MSBuild essaie d'être intelligent et n'introduit que des références dans le projet Y qu'il détecte comme étant requises par le projet X; il le fait pour éviter la pollution de référence dans le projet Y. Le problème est, puisque le projet X ne contient pas réellement de code qui utilise explicitement l'assembly B (par exemple B.SomeFunction ()), VS / MSBuild ne détecte pas que B est requis par X, et donc ne le copie pas dans le répertoire bin du projet Y; il copie uniquement les assemblages X et A.

Solution

Vous avez deux options pour résoudre ce problème, les deux entraînant la copie de l'assembly B dans le répertoire bin du projet Y:

  1. Ajoutez une référence à l'assemblage B dans le projet Y.
  2. Ajoutez du code factice à un fichier du projet X qui utilise l'assembly B.

Personnellement, je préfère l'option 2 pour quelques raisons.

  1. Si vous ajoutez à l'avenir un autre projet qui fait référence au projet X, vous n'aurez pas à vous rappeler d'inclure également une référence à l'assembly B (comme vous le feriez avec l'option 1).
  2. Vous pouvez avoir des commentaires explicites indiquant pourquoi le code factice doit être là et ne pas le supprimer. Donc, si quelqu'un supprime le code par accident (par exemple avec un outil de refactorisation qui recherche du code inutilisé), vous pouvez facilement voir depuis le contrôle de code source que le code est requis et le restaurer. Si vous utilisez l'option 1 et que quelqu'un utilise un outil de refactorisation pour nettoyer les références inutilisées, vous n'avez aucun commentaire; vous verrez simplement qu'une référence a été supprimée du fichier .csproj.

Voici un exemple du "code factice" que j'ajoute généralement lorsque je rencontre cette situation.

    // DO NOT DELETE THIS CODE UNLESS WE NO LONGER REQUIRE ASSEMBLY A!!!
    private void DummyFunctionToMakeSureReferencesGetCopiedProperly_DO_NOT_DELETE_THIS_CODE()
    {
        // Assembly A is used by this file, and that assembly depends on assembly B,
        // but this project does not have any code that explicitly references assembly B. Therefore, when another project references
        // this project, this project's assembly and the assembly A get copied to the project's bin directory, but not
        // assembly B. So in order to get the required assembly B copied over, we add some dummy code here (that never
        // gets called) that references assembly B; this will flag VS/MSBuild to copy the required assembly B over as well.
        var dummyType = typeof(B.SomeClass);
        Console.WriteLine(dummyType.FullName);
    }
chien mortel
la source
4
Ce qui n'est pas discuté ici, c'est qu'il y a une différence entre la copie des produits de construction dans / bin d'un projet Web et la copie de routine dans le répertoire de sortie cible (par exemple / bin / x86 / Debug). Le premier est effectué par le projet référencé lors de sa génération et le second par le projet Web dépendant. L'examen de Microsoft.Common.targets permet de comprendre cela. Les copies sur le web / bin ne dépendent pas du tout du comportement local de copie - copiez les impacts locaux sur la copie dans le répertoire cible de sortie qui ne fait pas partie de la structure référencée par Cassini exécutée via Debug.
user1164178
6
pouvez-vous expliquer pourquoi cela a fonctionné avec VS SANS ajouter ce "code factice" mais pas avec msbuild?
toebens
3
Encore moins invasif que d'invoquer une fonction, vous pouvez affecter le type d'une classe contenue à l'intérieur de l'assembly à une variable fictive. Type dummyType = typeof(AssemblyA.AnyClass);
Arithmomaniac
14
La solution n ° 2, comme indiqué ci-dessus, fonctionnera sauf si vous avez le paramètre «Optimiser le code» vérifié dans Visual Studio. Dans ce cas, il exclura toujours la DLL. J'ai ajouté une ligne de plus pour remplacer son "optimisation". Console.WriteLine(dummyType.FullName);
JasonG
3
La solution 2 ne fonctionnait pas pour moi dans Visual Studio 2017. J'ai d'abord pensé que c'était parce que, dans mon assembly X, j'utilisais uniquement un enumde B et j'ai supposé que l'énumération était en ligne. J'ai ajouté du code pour utiliser directement un type de B et cela n'a pas aidé. Il a également toujours été le cas où mon X utilise des types dans A qui sous-types dans B, donc je ne peux pas comprendre comment le compilateur pense que B n'est pas requis par X et peut être ignoré. Ce sont des bonkers.
Xharlie
170

Je le traite comme ça. Accédez aux propriétés de votre référence et procédez comme suit:

Set "Copy local = false"
Save
Set "Copy local = true"
Save

et c'est tout.

Visual Studio 2010 ne met pas initialement: <private>True</private> dans la balise de référence et la définition de "copier local" sur false provoque la création de la balise. Ensuite, il le réglera sur vrai et faux en conséquence.

andrew
la source
10
C'était une aubaine. Merci pour ça!
Rebecca
22
N'a pas fonctionné pour moi avec MSBuild 4 / VS2012. Autrement dit, j'ai pu mettre à jour les références pour le dire, <Private>true</Private>mais cela ne semblait avoir aucun effet sur MSBuild. Au final, je viens d'ajouter des références NuGet à des projets de niveau inférieur.
Michael Teper
2
Ça n'a pas marché pour moi. Il ne copie toujours pas System.Net.Http.Formatting dans le dossier bin.
Akira Yamamoto
4
C'est l'équivalent de Have you tried turning it off and on again?, et cela a fonctionné!
guanome
6
On dirait que VS2015 se comporte toujours de la même manière: définir "Copier local" sur "Faux" puis revenir à "Vrai" sur les fichiers .dll référencés, fonctionne.
retournez
38

Si vous n'utilisez pas l'assembly directement dans le code, alors Visual Studio, tout en essayant d'être utile, détecte qu'il n'est pas utilisé et ne l'inclut pas dans la sortie. Je ne sais pas pourquoi vous voyez un comportement différent entre Visual Studio et MSBuild. Vous pouvez essayer de définir la sortie de génération sur diagnostic pour les deux et comparer les résultats pour voir où ils divergent.

Quant à votre référence elmah.dll si vous ne la référencez pas directement dans le code, vous pouvez l'ajouter en tant qu'élément à votre projet et définir l'action de génération sur Contentet le répertoire de copie dans le répertoire de sortie sur Always.

John Hunter
la source
3
+1 pour votre copie dans le commentaire du répertoire de sortie, si Elmah n'est pas utilisé dans le code, il est logique de copier en tant que contenu.
Kit Roed du
4
En effet, il ignore les assemblys qui ne sont pas utilisés, MAIS une chose importante à noter, à partir de VS 2010, utiliser l'assemblage dans les dictionnaires de ressources XAML n'est pas considéré comme utilisant assimilation par VS, donc il ne le copiera pas.
Alex Burtsev
C'est mieux pour un projet de test unitaire où j'ai besoin de la DLL. Je ne veux pas ajouter une DLL dont le projet principal n'a pas besoin juste pour faire fonctionner les tests!
Lukos
14

Jeter un coup d'œil à:

Ce fil de discussion MSBuild que j'ai commencé

Vous y trouverez ma solution / solution temporaire!

(MyBaseProject a besoin d'un code qui fait référence à certaines classes (quoi que) de elmah.dll pour qu'elmah.dll soit copié dans le bac de MyWebProject1!)

toebens
la source
1
Merde - c'est la seule solution à laquelle je suis arrivé aussi - j'espérais qu'il y aurait une meilleure façon de le faire!
nickspoon
2
Voir la réponse d'Andrew ci-dessous pour une solution moins hacky (sans infraction!)
MPritchard
1
Pour ceux qui regardent cela maintenant, la réponse de toebens sur MSDN est essentiellement la même que la réponse de deadlydog , fournie quelques années plus tard.
jpaugh
8

J'ai eu le même problème.

Vérifiez si la version du framework de votre projet est la même que la version du framework de la DLL que vous mettez en référence.

Dans mon cas, mon client a été compilé à l'aide de "Framework 4 Client" et la DLL était dans "Framework 4".

Andre Avelar
la source
6

Le problème auquel j'étais confronté était que j'avais un projet qui dépend d'un projet de bibliothèque. Afin de construire, je suivais ces étapes:

msbuild.exe myproject.vbproj /T:Rebuild
msbuild.exe myproject.vbproj /T:Package

Cela signifiait bien sûr que je manquais les fichiers dll de ma bibliothèque dans le bac et, surtout, dans le fichier zip du package. J'ai trouvé que cela fonctionne parfaitement:

msbuild.exe myproject.vbproj /T:Rebuild;Package

Je n'ai aucune idée pourquoi ce travail ou pourquoi il ne l'a pas fait en premier lieu. Mais j'espère que ça aide.

vangorra
la source
J'ai eu le même problème lors de la création de la solution complète en utilisant / t: Build à partir de TeamCity en une seule étape, puis dans le prochain / t: Package sur le projet WebAPI. Aucune DLL référencée par une référence de projet n'a été incluse. Ce problème a été résolu à l'aide du package - / T: Rebuild; sur WebAPI ci-dessus, qui comprenait alors ces DLL.
Andy Hoyle
5

J'ai juste eu exactement le même problème et cela s'est avéré être dû au fait que 2 projets dans la même solution faisaient référence à une version différente de la bibliothèque tierce.

Une fois que j'ai corrigé toutes les références, tout fonctionnait parfaitement.

Steven Engels
la source
4

Comme Alex Burtsev l'a mentionné dans un commentaire, tout ce qui n'est utilisé que dans un dictionnaire de ressources XAML, ou dans mon cas, tout ce qui n'est utilisé qu'en XAML et pas dans le code derrière, n'est pas considéré comme `` utilisé '' par MSBuild.

Donc, le simple fait de créer une référence factice à une classe / composant dans l'assembly dans un code derrière était suffisant pour convaincre MSBuild que l'assembly était réellement utilisé.

Scott
la source
C'était exactement mon problème et la solution qui fonctionnait. J'ai passé trop de temps à essayer de comprendre cela. Merci Scott & @Alex Burstev
karol
Bon deuil, cela me rendait fou. Oui , j'utilisais FontAwesome.WPF, et uniquement depuis l'intérieur du XAML (pour des raisons évidentes). L'ajout d'une méthode factice a aidé. Merci! Et oui, VS 2017 15.6 est toujours affecté, j'ai donc déposé un bug: github.com/dotnet/roslyn/issues/25349
Sören Kuklau
3

La modification du cadre cible de .NET Framework 4 Client Profile à .NET Framework 4 a résolu ce problème pour moi.

Donc, dans votre exemple: définissez le framework cible sur MyWebProject1 sur .NET Framework 4

Fuyu Persimmon
la source
3

En utilisant le schéma de deadlydog,

Y => X => A => B ,

mon problème était quand j'ai construit Y, les assemblys (A et B, tous les 15 d'entre eux) à partir de X n'apparaissaient pas dans le dossier bin de Y.

Je l'ai résolu en supprimant la référence X de Y, en enregistrant, en construisant, puis en rajoutant la référence X (une référence de projet), et en enregistrant, en construisant et A et B ont commencé à apparaître dans le dossier bin de Y.

JZ
la source
Après plusieurs heures de recherche et d'essai de nombreuses autres solutions, celle-ci a fonctionné pour moi.
Suncat2000
2

J'ai eu le même problème et la DLL était une référence chargée dynamiquement. Pour résoudre le problème, j'ai ajouté une "utilisation" avec l'espace de noms de la DLL. Maintenant, la DLL est copiée dans le dossier de sortie.

Spamme
la source
2

Cela nécessite d'ajouter un .targets fichier à votre projet et de le configurer pour qu'il soit inclus dans la section des inclusions du projet.

Voir ma réponse ici pour la procédure.

Alex Essilfie
la source
2

Référencer des assemblys qui ne sont pas utilisés pendant la génération n'est pas la bonne pratique. Vous devez augmenter votre fichier de construction pour qu'il copie les fichiers supplémentaires. Soit en utilisant un événement post-construction, soit en mettant à jour le groupe de propriétés.

Quelques exemples peuvent être trouvés dans d'autres articles

verbedr
la source
2

Un autre scénario où cela apparaît est si vous utilisez l'ancien type de projet «Site Web» dans Visual Studio. Pour ce type de projet, il ne peut pas référencer des fichiers .dll qui se trouvent en dehors de sa propre structure de répertoires (dossier en cours et vers le bas). Donc, dans la réponse ci-dessus, disons que votre structure de répertoire ressemble à ceci:

entrez la description de l'image ici

Où ProjectX et ProjectY sont des répertoires parent / enfant et ProjectX fait référence à A.dll qui à son tour fait référence à B.dll et B.dll est en dehors de la structure de répertoires, comme dans un package Nuget à la racine (Packages), puis A. dll sera inclus, mais B.dll ne le sera pas.

Focker
la source
0

J'ai eu un problème similaire aujourd'hui, et ce n'est certainement pas la réponse à votre question. Mais j'aimerais informer tout le monde et éventuellement fournir une étincelle de perspicacité.

J'ai une application ASP.NET. Le processus de génération est défini pour nettoyer puis générer.

J'ai deux scripts Jenkins CI . Un pour la production et un pour la mise en scène. J'ai déployé mon application sur la mise en scène et tout a bien fonctionné. Déployé en production et il manquait un fichier DLL référencé. Ce fichier DLL était juste à la racine du projet. Pas dans aucun référentiel NuGet. La DLL a été définie sur do not copy.

Le script CI et l'application étaient les mêmes entre les deux déploiements. Toujours après le nettoyage et le déploiement dans l'environnement de transfert, le fichier DLL a été remplacé à l'emplacement de déploiement de l'application ASP.NET ( bin/). Ce n'était pas le cas pour l'environnement de production.

Il s'avère que dans une branche de test, j'avais ajouté une étape au processus de génération pour copier sur ce fichier DLL dans le bin répertoire. Maintenant, la partie qui a pris un peu de temps à comprendre. Le processus CI ne se nettoyait pas lui-même. La DLL a été laissée dans le répertoire de travail et était accidentellement empaquetée avec le fichier ASP.NET .zip. La branche de production n'a jamais copié le fichier DLL de la même manière et ne l'a jamais déployé accidentellement.

TLDR; Vérifiez et assurez-vous de savoir ce que fait votre serveur de build.

Jason Portnoy
la source
0

Assurez-vous que les deux projets sont dans la même version .net vérifiez également la copie de la propriété locale, mais cela devrait être truepar défaut

john.kernel
la source
0

Utilisation de Visual Studio 2015 en ajoutant le paramètre supplémentaire

/ deployonbuild = false

à la ligne de commande msbuild a résolu le problème.

Giles Roberts
la source
-1

Je viens de rencontrer un problème très similaire. Lors de la compilation à l'aide de Visual Studio 2010, le fichier DLL était inclus dans le bindossier. Mais lors de la compilation à l'aide de MSBuild, le fichier DLL tiers n'était pas inclus.

Très frustrant. La façon dont je l'ai résolu était d'inclure la référence NuGet au package dans mon projet Web même si je ne l'utilise pas directement là-bas.

Gary Brunton
la source
Ceci est un double de la réponse la plus élevée.
Giles Roberts
-3

L'inclusion de tous les fichiers DLL référencés de vos références de projet dans le projet de site Web n'est pas toujours une bonne idée, en particulier lorsque vous utilisez l' injection de dépendance : votre projet Web veut simplement ajouter une référence au fichier / projet DLL d'interface, pas toute DLL d'implémentation concrète fichier.

Parce que si vous ajoutez une référence directement à un fichier / projet DLL d'implémentation, vous ne pouvez pas empêcher votre développeur d'appeler un "nouveau" sur des classes concrètes du fichier / projet DLL d'implémentation plutôt que via l'interface. C'est aussi que vous avez indiqué un "hardcode" dans votre site Web pour utiliser l'implémentation.

Hung Nguyen
la source