Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

18 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 28 de enero de 2025
Un brazo robótico llamado NuGet gestionando paquetes en un almacén

Habitualmente los paquetes NuGet que usamos en un proyecto se registran en entradas <PackageReference> de su archivo .csproj. Es un mecanismo sencillo y efectivo cuando trabajamos con un único proyecto o un número reducido de ellos.

Sin embargo, cuando la cosa crece y estamos trabajando con muchos proyectos que usan paquetes comunes cuyas versiones deben estar alineadas, todo se complica un poco, y trabajar con ellos se puede convertir en algo bastante tedioso. Y más aún si no usamos una herramienta visual como NuGet Package Manager de Visual Studio.

Para estos escenarios, NuGet 6.2, lanzado en agosto de 2023 y presente por defecto en todas las instalaciones actualizadas de .NET y Visual Studio, introdujo el concepto de gestión centralizada de paquetes (en inglés, Central Package Management o CPM). Esta funcionalidad nos brinda la posibilidad de definir en un archivo independiente los paquetes utilizados, con sus versiones correspondientes, de forma que todos los proyectos de la solución puedan hacer referencia a él y mantener sus dependencias actualizadas.

Funcionamiento básico de la gestión centralizada de paquetes

Cuando usamos la gestión centralizada de paquetes, el archivo Directory.Packages.props es el encargado de especificar los paquetes y versiones exactas a utilizar en los proyectos a los que se aplica. Internamente, se trata de un archivo XML similar a los .csproj, en cuyo interior encontraremos las referencias a los paquetes.

Un ejemplo básico de contenido de este archivo podría ser el siguiente, donde definimos que los proyectos afectados por el archivo deben usar la versión 13.0.1 de AutoMapper:

<Project>
    <ItemGroup>
        <PackageVersion Include="AutoMapper" Version="13.0.1" />
        <!-- Otros paquetes... -->
    </ItemGroup>
</Project>

Una vez tenemos el archivo creado, debemos indicar en cada proyecto que queremos que use la gestión centralizada de paquetes. Para ello, añadimos una referencia a este archivo en el .csproj estableciendo a true la propiedad ManagePackageVersionsCentrally:

<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>
  ...
</Project>

Adicionalmente, debemos eliminar la versión de las entradas <PackageReference> del proyecto, dejando indicado únicamente su identificador, es decir, el nombre del paquete. De esta forma, NuGet buscará la versión correspondiente en el archivo Directory.Packages.props:

<Project>
    <PropertyGroup>
        <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="AutoMapper" />
        <!-- Otros paquetes... -->
    </ItemGroup>
</Project>

A partir de este momento, un cambio de versión en el archivo Directory.Packages.props se propagará automáticamente a todos los proyectos que hagan referencia a él, sin necesidad de modificar manualmente cada uno de ellos. Esto funcionará tanto manualmente como usando el gestor de paquetes de Visual Studio.

Un detalle a tener muy en cuenta es la ubicación de este archivo. A la hora de restaurar los paquetes de un proyecto, NuGet utilizará la información indicada en el archivo Directory.Packages.props ubicado en el directorio raíz del proyecto; si no existe, se buscará en directorios superiores hasta encontrarlo o dar la búsqueda por finalizada. Esto permite un control muy preciso de cómo queremos gestionar las dependencias, porque dependiendo de su ubicación podríamos usarla por proyecto, por solución, por grupo de proyectos, etc. Sólo dependerá de dónde hayamos decidido colocarlo.

Pero CPM tiene otros detalles interesantes...

Otras posibilidades de la gestión centralizada de paquetes

En este punto ya conocemos lo básico para trabajar con CPM en la mayoría de escenarios. Sin embargo, hay otras funcionalidades que pueden resultar útiles conocer para sacarle el máximo partido

Por ejemplo, la gestión centralizada de paquetes puede evitarnos algo de trabajo gracias a la posibilidad de definir paquetes globales, es decir, paquetes que se añadirán a todos los proyectos afectados por el archivo Directory.Packages.props sin tener que referenciarlos explícitamente en cada uno de ellos.

Para ello, utilizaremos la etiqueta <GlobalPackageReference> para indicar el paquete y versión a instalar:

<ItemGroup>
    <GlobalPackageReference Include="Serilog" Version="4.1.0" />
</ItemGroup>    

También es interesante saber que, si por cualquier motivo un proyecto necesita usar una versión específica de un paquete distinta a la indicada en el archivo Directory.Packages.props, podemos sobrescribir la versión en el propio proyecto. Para ello, añadimos una entrada <PackageReference> con la versión deseada, y NuGet usará esta versión en lugar de la indicada en el archivo central:

<ItemGroup>
    <PackageReference Include="Serilog" VersionOverride="3.0.0" />
</ItemGroup>

Otra posibilidad, que podría venir bien si trabajamos con proyectos muy grandes y estructurados jerárquicamente a nivel de carpetas y paquetes, es que desde un Directory.Packages.props podemos incluir el contenido de otros Directory.Packages.props ubicados en distinta localización.

Por ejemplo, a continuación vemos cómo podríamos incluir el contenido del archivo Directory.Packages.props ubicado en el directorio anterior al actual:

<Project>
    <Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Packages.props, $(MSBuildThisFileDirectory)..))" />
    <ItemGroup>
        <PackageReference Include="Serilog" VersionOverride="3.0.0" />    
    </ItemGroup>
</Project>

La gestión centralizada de paquetes también puede gestionar automáticamente las versiones de las dependencias transitivas, es decir, de paquetes que dependen de otros paquetes. Estableciendo a cierta la propiedad CentralPackageTransitivePinningEnabled, podemos forzar que estos paquetes usen la versión establecida en el archivo Directory.Packages.props:

<PropertyGroup>
  <CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>

¡Espero que os sea de utilidad!

Publicado en: www.variablenotfound.com.

2 Comentarios:

MontyCLT dijo...

Desconocía esto, pero sí que es cierto que yo desde hace tiempo llevo haciendo algo similar.

Tengo a nivel de solución algunos ficheros .props que, además de paquetes, también tienen otras propiedades y que luego, importo en los distintos proyectos con un simple

Desde luego, con lo que MSBuild proporciona por sí mismo, creo que se puede lograr el mismo efecto.

José María Aguilar dijo...

Hola Iván!

En efecto, con build.props podíamos conseguir algo parecido, aunque su objetivo era más generalista, es decir, enfocado a prácticamente cualquier propiedad del proyecto, pero "desconectado" de NuGet, lo que creaba problemas para mantener ambos mundos sincronizados. Sin embargo, dado que CPM es una característica específica del gestor de paquetes pone muy fácil las tareas de actualizar, alinear versiones, definir excepciones, paquetes globales, etc, y compatible con los entornos de desarrollo.

En definitiva, si te va bien con los .props, ya sabes... si te funciona, no lo toques ;) Aunque en cualquier caso creo que es interesante echar un vistazo a esta opción.

Saludos & gracias por comentar!