MSBuild no copia referencias (archivos DLL) si usa dependencias del proyecto en la solución

273

Tengo cuatro proyectos en mi solución de Visual Studio (todos dirigidos a .NET 3.5). Para mi problema, solo estos dos son importantes:

  1. MyBaseProject <: esta biblioteca de clases hace referencia a un archivo DLL de terceros (elmah.dll)
  2. MyWebProject1 <- este proyecto de aplicación web tiene una referencia a MyBaseProject

Agregué la referencia elmah.dll a MyBaseProject en Visual studio 2008 haciendo clic en "Agregar referencia ..." → pestaña "Examinar" → seleccionando "elmah.dll".

Las propiedades de la referencia de Elmah son las siguientes:

  • Alias ​​- global
  • Copiar local - verdadero
  • Cultura -
  • Descripción - Módulos y controladores de registro de errores (ELMAH) para ASP.NET
  • Tipo de archivo - Ensamblaje
  • Ruta - D: \ webs \ otherfolder \ _myPath \ __ tools \ elmah \ Elmah.dll
  • Resuelto - Verdadero
  • Versión de tiempo de ejecución - v2.0.50727
  • Versión especificada - falso
  • Nombre fuerte - falso
  • Versión - 1.0.11211.0

En MyWebProject1 agregué la referencia al Proyecto MyBaseProject: "Agregar referencia ..." → pestaña "Proyectos" → seleccionando "MyBaseProject". Las propiedades de esta referencia son las mismas, excepto los siguientes miembros:

  • Descripción -
  • Ruta - D: \ webs \ CMS \ MyBaseProject \ bin \ Debug \ MyBaseProject.dll
  • Versión - 1.0.0.0

Si ejecuto la compilación en Visual Studio, el archivo elmah.dll se copia en el directorio bin de MyWebProject1 , junto con MyBaseProject.dll.

Sin embargo, si limpio y ejecuto MSBuild para la solución (a través de D: \ webs \ CMS> C: \ WINDOWS \ Microsoft.NET \ Framework \ v3.5 \ MSBuild.exe / t: ReBuild / p: Configuration = Debug MyProject.sln ) falta el elmah.dll en el directorio bin de MyWebProject1, ¡aunque la compilación en sí no contiene advertencias ni errores!

Ya me aseguré de que el .csproj de MyBaseProject contenga el elemento privado con el valor "verdadero" (que debería ser un alias para " copiar local " en 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 etiqueta privada no apareció en el xml de .csproj de manera predeterminada, aunque Visual Studio dijo "copiar local" verdadero. Cambié "copiar local" a falso - guardado - y lo configuré nuevamente como verdadero - ¡guardar!)

¿Qué le pasa a MSBuild? ¿Cómo obtengo la referencia (elmah.dll) copiada en el contenedor de MyWebProject1?

¡NO quiero agregar una acción de copia posterior a la construcción al comando de construcción posterior de cada proyecto! (¡Imagínese que muchos proyectos dependerían de MyBaseProject!)

dedos de los pies
fuente
11
Me encantaría obtener una respuesta más clara de por qué sucede esto.
David Faivre
1
Eche un vistazo a la respuesta proporcionada aquí
Anuroopa Shenoy
1
¿Alguna solución final con una muestra de código fuente completa trabajando al respecto?
Kiquenet
2
Vea la respuesta stackoverflow.com/a/21055664/21579 a continuación por @deadlydog. Excelente explicación y resolvió el problema para mí ... la respuesta más votada a continuación no es correcta para VS2012.
Jeff Widmer

Respuestas:

153

No estoy seguro de por qué es diferente al construir entre Visual Studio y MsBuild, pero esto es lo que encontré cuando me encontré con este problema en MsBuild y Visual Studio.

Explicación

Para un escenario de ejemplo, supongamos que tenemos el proyecto X, el conjunto A y el conjunto B. El conjunto A hace referencia al conjunto B, por lo que el proyecto X incluye una referencia a A y B. Además, el proyecto X incluye código que hace referencia al conjunto A (por ejemplo, A. SomeFunction ()). Ahora, crea un nuevo proyecto Y que hace referencia al proyecto X.

Entonces la cadena de dependencia se ve así: Y => X => A => B

Visual Studio / MSBuild intenta ser inteligente y solo trae referencias al proyecto Y que detecta como requeridas por el proyecto X; lo hace para evitar la contaminación de referencia en el proyecto Y. El problema es que, dado que el proyecto X en realidad no contiene ningún código que utilice explícitamente el ensamblado B (por ejemplo, B.SomeFunction ()), VS / MSBuild no detecta que se requiere B por X, y por lo tanto no lo copia en el directorio bin del proyecto Y; solo copia los ensamblajes X y A.

Solución

Tiene dos opciones para resolver este problema, las cuales resultarán en que el ensamblado B se copie en el directorio bin del proyecto Y:

  1. Agregue una referencia al ensamblado B en el proyecto Y.
  2. Agregue código ficticio a un archivo en el proyecto X que usa el ensamblado B.

Personalmente, prefiero la opción 2 por un par de razones.

  1. Si agrega otro proyecto en el futuro que haga referencia al proyecto X, no tendrá que recordar incluir también una referencia al ensamblado B (como tendría que ver con la opción 1).
  2. Puede tener comentarios explícitos que indiquen por qué el código ficticio debe estar allí y no eliminarlo. Entonces, si alguien elimina el código por accidente (por ejemplo, con una herramienta de refactorización que busca código no utilizado), puede ver fácilmente desde el control de origen que se requiere el código y restaurarlo. Si usa la opción 1 y alguien usa una herramienta de refactorización para limpiar referencias no utilizadas, no tiene ningún comentario; solo verá que se eliminó una referencia del archivo .csproj.

Aquí hay una muestra del "código ficticio" que normalmente agrego cuando me encuentro con esta situación.

    // 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);
    }
perro mortal
fuente
44
Lo que no se está discutiendo aquí es que existe una diferencia entre la copia de productos de compilación en / bin de un proyecto web y la copia de rutina en el directorio de salida de destino (por ejemplo, / bin / x86 / Debug). El primero lo realiza el proyecto referenciado cuando se construye y el segundo lo hace el proyecto web dependiente. Examinar Microsoft.Common.targets ayuda a entender esto. Las copias en la web / bin no dependen en absoluto del comportamiento local de la copia: copie los impactos locales en la copia en el directorio de destino de salida que no forma parte de la estructura a la que hace referencia Cassini que se ejecuta a través de Debug.
user1164178
66
¿Puedes explicar por qué había funcionado con VS SIN agregar ese "código ficticio" pero no con msbuild?
toebens
3
Incluso menos invasivo que invocar una función, puede asignar el tipo de una clase contenida dentro del ensamblaje a una variable ficticia. Type dummyType = typeof(AssemblyA.AnyClass);
Arithmomaniac
14
La solución n. ° 2 como se muestra arriba funcionará a menos que tenga la configuración 'Optimizar código' marcada en Visual Studio. En ese caso, seguirá excluyendo el dll. Agregué una línea más para anular su "optimización". Console.WriteLine(dummyType.FullName);
JasonG
3
La solución 2 no funcionó para mí en Visual Studio 2017. Primero pensé que era porque, en mi ensamblaje X, solo estaba usando una enumde B y supuse que la enumeración estaba en línea. Agregué código para usar directamente un tipo de B y eso no ayudó. También siempre ha sido el caso de que mi X usa tipos en A que subclases en B, por lo que no puedo entender cómo el compilador piensa que B no es requerido por X y puede ser ignorado. Esto es una locura.
Xharlie
170

Solo lo trato así. Vaya a las propiedades de su referencia y haga esto:

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

y eso es.

Visual Studio 2010 no pone inicialmente: <private>True</private> en la etiqueta de referencia y establecer "copiar local" en falso hace que cree la etiqueta. Luego lo establecerá en verdadero y falso en consecuencia.

Andrés
fuente
10
Esto fue un regalo del cielo. ¡Gracias por esto!
Rebecca
22
No funcionó para mí con MSBuild 4 / VS2012. Es decir, pude actualizar las referencias para decir, <Private>true</Private>pero parecía no tener ningún efecto en MSBuild. Al final, acabo de agregar referencias NuGet a proyectos de nivel inferior.
Michael Teper el
2
No funciono para mi. Todavía no copia System.Net.Http.Formatting a la carpeta bin.
Akira Yamamoto
44
Este es el equivalente a Have you tried turning it off and on again?, y funcionó!
guanome
66
Parece que VS2015 todavía se comporta igual: configura 'Copiar local' a 'Falso' y luego vuelve a 'Verdadero' en los .dll de referencia, funciona.
voltear el
38

Si no está utilizando el ensamblado directamente en el código, Visual Studio, mientras intenta ser útil, detecta que no se utiliza y no lo incluye en la salida. No estoy seguro de por qué está viendo un comportamiento diferente entre Visual Studio y MSBuild. Puede intentar configurar la salida de compilación para el diagnóstico para ambos y comparar los resultados para ver dónde diverge.

En cuanto a su referencia de elmah.dll, si no hace referencia directa a él en el código, puede agregarlo como un elemento a su proyecto y establecer la Acción de compilación en Contenty el Directorio Copiar a salida en Always.

John Hunter
fuente
3
+1 para su copia al comentario del Directorio de salida, si Elmah no se usa en el código, tiene sentido copiarlo como contenido.
Kit Roed
44
De hecho, ignora los ensamblajes que no se usan, PERO una cosa importante a tener en cuenta, a partir de VS 2010, el uso de ensamblaje en los diccionarios de recursos XAML no se considera que VS lo use de manera asimilable, por lo que no lo copiará.
Alex Burtsev
Esto es mejor para un proyecto de prueba de unidad donde necesito el dll. ¡No quiero agregar un dll que el proyecto principal no necesita solo para ejecutar las pruebas!
Lukos
14

Echa un vistazo a:

Este hilo del foro de MSBuild que empecé

¡Encontrarás mi solución / solución temporal allí!

(¡MyBaseProject necesita un código que haga referencia a algunas clases (lo que sea) del elmah.dll para que elmah.dll se copie al contenedor de MyWebProject1!)

dedos de los pies
fuente
1
Maldición, esta es la única solución a la que también he llegado, ¡esperaba que hubiera una mejor manera de hacerlo!
nickspoon
2
Vea la respuesta de Andrew a continuación para obtener una solución menos hacky (¡sin ofender!)
MPritchard
1
Para aquellos que miran esto ahora, la respuesta de toebens en MSDN es esencialmente la misma que la respuesta de deadlydog , proporcionada algunos años después.
jpaugh
8

Yo tuve el mismo problema.

Compruebe si la versión de marco de su proyecto es la misma que la versión de marco de la dll que ha puesto como referencia.

En mi caso, mi cliente fue compilado usando "Framework 4 Client" y la DLL estaba en "Framework 4".

Andre Avelar
fuente
6

El problema que enfrentaba era que tenía un proyecto que dependía de un proyecto de biblioteca. Para construir estaba siguiendo estos pasos:

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

Por supuesto, eso significaba que me faltaban los archivos dll de mi biblioteca en bin y lo más importante en el archivo zip del paquete. Encontré que esto funciona perfectamente:

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

No tengo idea de por qué esto funciona o por qué no funcionó en primer lugar. Pero espero que eso ayude.

vanguardia
fuente
Tuve el mismo problema al crear una solución completa usando / t: Construir desde TeamCity en un paso y luego en el siguiente / t: Paquete en el proyecto WebAPI. No se incluyeron los dlls a los que hace referencia cualquier referencia de proyecto. Esto se solucionó utilizando el anterior - / T: Reconstruir; El paquete en el WebAPI incluyó esos dlls.
Andy Hoyle
5

Acabo de tener exactamente el mismo problema y resultó ser el hecho de que 2 proyectos en la misma solución hacían referencia a una versión diferente de la biblioteca de terceros.

Una vez que corregí todas las referencias, todo funcionó perfectamente.

Steven Engels
fuente
4

Como Alex Burtsev mencionó en un comentario, todo lo que solo se usa en un diccionario de recursos XAML, o en mi caso, todo lo que solo se usa en XAML y no en el código subyacente, MSBuild no lo considera "en uso".

Entonces, simplemente actualizar una referencia ficticia a una clase / componente en el ensamblaje en algún código detrás fue suficiente para convencer a MSBuild de que el ensamblaje estaba realmente en uso.

Scott
fuente
Este fue exactamente mi problema y la solución que funcionó. Pasé demasiado tiempo tratando de resolver esto. Gracias Scott y @Alex Burstev
karol
Dios mío, esto me estaba volviendo loco. , estaba usando FontAwesome.WPF, y solo desde XAML (por razones obvias). Agregar un método ficticio ayudó. ¡Gracias! Y sí, VS 2017 15.6 todavía está afectado, así que presenté un error: github.com/dotnet/roslyn/issues/25349
Sören Kuklau
3

Cambiar el marco de destino de .NET Framework 4 Client Profile a .NET Framework 4 solucionó este problema.

Entonces, en su ejemplo: establezca el marco de destino en MyWebProject1 en .NET Framework 4

Caqui Fuyu
fuente
3

Usando el esquema de deadlydog,

Y => X => A => B ,

mi problema fue cuando construí Y, los ensamblajes (A y B, los 15) de X no aparecían en la carpeta bin de Y.

Lo resolví eliminando la referencia X de Y, guardar, compilar, luego volver a agregar la referencia X (una referencia de proyecto) y guardar, compilar, y A y B comenzaron a aparecer en la carpeta bin de Y.

JZ
fuente
Después de muchas horas de buscar y probar muchas de las otras soluciones, esta funcionó para mí.
Suncat2000
2

Tuve el mismo problema y el dll era una referencia cargada dinámicamente. Para resolver el problema, he agregado un "uso" con el espacio de nombres de la dll. Ahora el dll se copia en la carpeta de salida.

Spamme
fuente
2

Esto requiere agregar un .targetsarchivo a su proyecto y configurarlo para que se incluya en la sección incluye del proyecto.

Vea mi respuesta aquí para el procedimiento.

Alex Essilfie
fuente
2

Hacer referencia a ensamblajes que no se usan durante la compilación no es la práctica correcta. Debe aumentar su archivo de compilación para que copie los archivos adicionales. Ya sea utilizando un evento posterior a la compilación o actualizando el grupo de propiedades.

Algunos ejemplos se pueden encontrar en otra publicación

verbedr
fuente
2

Otro escenario en el que esto aparece es si está utilizando el tipo de proyecto "Sitio web" anterior en Visual Studio. Para ese tipo de proyecto, no puede hacer referencia a .dlls que están fuera de su propia estructura de directorios (carpeta actual y abajo). Entonces, en la respuesta anterior, digamos que la estructura de su directorio se ve así:

ingrese la descripción de la imagen aquí

Donde ProjectX y ProjectY son directorios padre / hijo, y ProjectX hace referencia a A.dll que a su vez hace referencia a B.dll, y B.dll está fuera de la estructura del directorio, como en un paquete Nuget en la raíz (Paquetes), luego A. dll se incluirá, pero B.dll no.

Focker
fuente
0

Tuve un problema similar hoy, y esta ciertamente no es la respuesta a su pregunta. Pero me gustaría informar a todos, y posiblemente proporcionar una chispa de perspicacia.

Tengo una aplicación ASP.NET. El proceso de compilación está configurado para limpiar y luego compilar.

Tengo dos scripts de Jenkins CI . Uno para producción y otro para puesta en escena. Implementé mi aplicación en la puesta en escena y todo funcionó bien. Implementado en producción y faltaba un archivo DLL al que se hacía referencia. Este archivo DLL estaba justo en la raíz del proyecto. No en ningún repositorio de NuGet. La DLL se configuró en do not copy.

El script de CI y la aplicación fueron iguales entre las dos implementaciones. Aún después de la limpieza e implementación en el entorno provisional, el archivo DLL se reemplazó en la ubicación de implementación de la aplicación ASP.NET ( bin/). Este no fue el caso para el entorno de producción.

Resulta que en una rama de prueba había agregado un paso al proceso de compilación para copiar este archivo DLL en el bindirectorio. Ahora la parte que tardó un poco en darse cuenta. El proceso de CI no se estaba limpiando a sí mismo. La DLL se dejó en el directorio de trabajo y se empaquetó accidentalmente con el archivo .zip de ASP.NET. La rama de producción nunca tuvo el archivo DLL copiado de la misma manera y nunca lo implementó accidentalmente.

TLDR; Verifique y asegúrese de saber qué está haciendo su servidor de compilación.

Jason Portnoy
fuente
0

Asegúrese de que ambos proyectos estén en la misma versión .net, también verifique la propiedad local de copia, pero esto debería ser el truepredeterminado

john.kernel
fuente
0

Usando Visual Studio 2015 agregando el parámetro adicional

/ deployonbuild = false

a la línea de comando msbuild solucionó el problema.

Giles Roberts
fuente
-1

Me encontré con un problema muy similar. Al compilar con Visual Studio 2010, el archivo DLL se incluyó en la bincarpeta. Pero al compilar usando MSBuild, el archivo DLL de terceros no estaba incluido.

Muy frustrante. La forma en que lo resolví fue incluir la referencia de NuGet al paquete en mi proyecto web a pesar de que no lo estoy usando directamente allí.

Gary Brunton
fuente
Este es un duplicado de la respuesta principal.
Giles Roberts
-3

Incluir todos los archivos DLL a los que se hace referencia desde las referencias de su proyecto en el proyecto del sitio web no siempre es una buena idea, especialmente cuando está utilizando inyección de dependencia : su proyecto web solo desea agregar una referencia al archivo / proyecto de interfaz DLL, no cualquier DLL de implementación concreta expediente.

Porque si agrega una referencia directamente a un archivo / proyecto DLL de implementación, no puede evitar que su desarrollador llame a un "nuevo" en clases concretas del proyecto / archivo DLL de implementación en lugar de hacerlo a través de la interfaz. También ha indicado un "código duro" en su sitio web para usar la implementación.

Hung Nguyen
fuente