visual studio net microsoft manually manager how c# .net nuget dependency-management

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 de Unity ?)

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 a DomainModel y SomeFancyMessagingLibrary )
  • 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.