c# - net - nuget package manager visual studio 2017
Cómo resolver el infierno de la dependencia de NuGet (2)
Unity
paquete Unity
no es un buen ejemplo porque solo debe usarlo en un lugar llamado Raíz de composición . Y la Composition Root
debe estar lo más cerca posible del punto de entrada de la aplicación. En su ejemplo es CompanyName.SomeSolution.Application
Aparte de eso, donde trabajo ahora, aparece exactamente el mismo problema. Y lo que veo, el problema se presenta a menudo por cuestiones transversales como el registro. La solución que puede aplicar es convertir sus dependencias de terceros en dependencias de terceros. Puedes hacerlo introduciendo abstracciones para esos conceptos. En realidad, hacer esto tiene otros beneficios como:
- código más mantenible
- mejor probabilidad
- deshacerse de la dependencia no deseada (cada cliente de
CompanyName.SDK
realmente necesita la dependencia deUnity
?)
Entonces, tomemos como ejemplo una biblioteca de .NET Logging
imaginaria:
CompanyName.SDK.dll
depende de .NET Logging 3.0
CompanyName.SomeSolution.Project1
depende de .NET Logging 2.0
CompanyName.SomeSolution.Project2
depende de .NET Logging 1.0
Hay cambios de última hora entre las versiones de .NET Logging
.
Puede crear su propia dependencia de primera persona introduciendo la interfaz de ILogger
:
public interface ILogger
{
void LogWarning();
void LogError();
void LogInfo();
}
CompanyName.SomeSolution.Project1
y CompanyName.SomeSolution.Project2
deben usar la interfaz ILogger
. Son dependientes de la dependencia de primera parte de la interfaz ILogger
. Ahora mantienes esa biblioteca de .NET Logging
detrás de un lugar y es fácil de actualizar porque tienes que hacerlo en un lugar. Además, romper los cambios entre versiones ya no es un problema, porque se usa una versión de la biblioteca de .NET Logging
de .NET Logging
.
La implementación real de la interfaz de ILogger
debe estar en un ensamblaje diferente y debe ser el único lugar donde se hace referencia a la biblioteca de .NET Logging
. En CompanyName.SomeSolution.Application
en el lugar donde compone su aplicación, ahora debe asignar la abstracción de ILogger
a la implementación concreta.
Estamos utilizando ese enfoque y también estamos utilizando NuGet
para distribuir nuestras abstracciones y nuestras implementaciones. Desafortunadamente, los problemas con las versiones pueden aparecer con sus propios paquetes. Para evitar que los problemas se apliquen las versiones semánticas en los paquetes que implementa a través de NuGet
para su empresa. Si algo cambia en su base de código que se distribuye a través de NuGet
, debe cambiar en todos los paquetes que se distribuyen a través de NuGet
. Por ejemplo, tenemos en nuestro servidor NuGet
local:
-
DomainModel
-
Services.Implementation.SomeFancyMessagingLibrary
(que hace referencia aDomainModel
ySomeFancyMessagingLibrary
) - y más...
La versión entre estos paquetes se sincroniza, si la versión se cambia en DomainModel
, la misma versión se encuentra en Services.Implementation.SomeFancyMessagingLibrary
. Si nuestras aplicaciones necesitan actualización de nuestros paquetes internos, todas las dependencias se actualizan a la misma versión.
Desarrollé una biblioteca con un nombre funcional CompanyName.SDK
que se debe integrar en el proyecto de la empresa CompanyName.SomeSolution
CompanyName.SDK.dll
debe implementarse a través del paquete NuGet. Y el paquete CompanyName.SDK
depende de los paquetes de terceros de NuGet. Para un buen ejemplo, tomemos la Unity
. La dependencia actual está en v3.5.1405-prerelease
de Unity
.
CompanyName.SomeSolution.Project1
depende de Unity
v2.1.505.2
. CompanyName.SomeSolution.Project2
depende de Unity
v3.0.1304.1
.
La integración de CompanyName.SDK
en esta solución agrega dependencia en Unity
v3.5.1405-prerelease
. Tomemos que CompanyName.SomeSolution
tiene un proyecto de salida ejecutable CompanyName.SomeSolution.Application
que depende de dos arriba y de CompanyName.SDK
Y aquí comienzan los problemas. Todos los ensamblados de Unity
tienen nombres iguales en todos los paquetes sin especificador de versión. Y en la carpeta de destino solo habrá una versión de los conjuntos de Unity
: v3.5.1405-prerelease
través de bindingRedirect
en app.config
.
¿Cómo pueden los códigos en Project1
, Project2
y SDK
usar exactamente las versiones necesarias de los paquetes dependientes con los que fueron codificados, compilados y probados?
NOTA 1: Unity
es solo un ejemplo, la situación real es 10 veces peor, ya que los módulos de 3rdparty dependen de otros módulos de 3rdparty que, a su vez, tienen 3-4 versiones simultáneamente.
NOTA2: No puedo actualizar todos los paquetes a sus últimas versiones porque hay paquetes que tienen dependencias de la última versión de otros paquetes.
NOTA3: Supongamos que los paquetes dependientes tienen cambios de última hora entre las versiones. Es el verdadero problema por qué estoy haciendo esta pregunta.
NOTA 4: Sé que hay preguntas sobre conflictos entre diferentes versiones del mismo ensamblaje dependiente, pero las respuestas allí no resuelven la raíz de un problema, simplemente lo ocultan.
NOTE5: ¿Dónde diablos está esa solución de problema prometida "DLL Hell"? Simplemente está reapareciendo desde otra posición.
NOTA 6: Si piensa que usar GAC es de alguna manera una opción, escriba una guía paso a paso o envíeme un enlace.
Puede trabajar en el nivel de ensamblaje posterior a la compilación para resolver este problema con ...
Opción 1
Puedes intentar fusionar los ensamblajes con ILMerge
ilmerge / target: winexe /out:SelfContainedProgram.exe Program.exe ClassLibrary1.dll ClassLibrary2.dll
El resultado será un ensamblaje que es la suma de su proyecto y sus dependencias requeridas. Esto conlleva algunos inconvenientes, como sacrificar el soporte mono y perder identidades de ensamblaje (nombre, versión, cultura, etc.), por lo que es mejor que todos los ensamblajes que se fusionen sean creados por usted.
Así que aquí viene ...
opcion 2
En su lugar, puede incrustar las dependencias como recursos dentro de sus proyectos como se describe en este artículo . Aquí está la parte relevante (los énfasis son míos):
En tiempo de ejecución, el CLR no podrá encontrar los ensamblados DLL dependientes, lo cual es un problema. Para solucionar esto, cuando su aplicación se inicialice, registre un método de devolución de llamada con el evento ResolveAssembly del dominio de aplicación . El código debe verse algo como esto:
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);
}
};
Ahora, la primera vez que un subproceso llama a un método que hace referencia a un tipo en un archivo DLL dependiente, se generará el evento AssemblyResolve y el código de devolución de llamada que se muestra arriba encontrará el recurso DLL incrustado deseado y lo cargará llamando a una sobrecarga del método Load del Assembly. que toma un byte [] como argumento.
Creo que esta es la opción que usaría si estuviera en su lugar, sacrificando un tiempo de inicio inicial.
Actualizar
Echa un vistazo here . También puede intentar usar esas etiquetas <probing>
en la aplicación.config de cada proyecto para definir una subcarpeta personalizada para buscar cuando CLR busca ensamblajes.