Selección de ensamblados a los que hacen referencia proyectos

Los ensamblados se usan de dos maneras diferentes durante una compilación. La primera es para la compilación, que permite que el código del consumidor del paquete se compile con las API del ensamblado y para que IntelliSense dé sugerencias. El segundo es para el entorno de ejecución, donde se copia el ensamblado en el directorio bin y se usa durante la ejecución del programa. A algunos autores de paquetes les gustaría que solo estuviesen disponibles sus propios ensamblados (o un subconjunto de sus ensamblados) para los consumidores del paquete en tiempo de compilación, pero necesitan proporcionar todas sus dependencias para el tiempo de ejecución. En este documento se examinan las formas de lograr este resultado.

Nuestra recomendación es tener un paquete por ensamblado y empaquetar las dependencias a otros ensamblados. Cuando NuGet restaura un proyecto, realiza la selección de recursos y admite la inclusión, la exclusión y la conversión en privados de recursos privados diferentes. Para evitar que las dependencias del paquete se conviertan en recursos en tiempo de compilación para cualquier persona que use el paquete, puede hacer que los recursos de compile sean privados. En el paquete generado, esto hará que compile se excluya de la dependencia. Tenga en cuenta que los recursos privados predeterminados cuando no se proporciona ninguno son contentfiles;build;analyzers. Por lo tanto, debe usar PrivateAssets="compile;contentfiles;build;analyzers" en PackageReference o ProjectReference.

<ItemGroup>
  <ProjectReference Include="..\OtherProject\OtherProject.csproj" PrivateAssets="compile;contentfiles;build;analyzers" />
  <PackageReference Include="SomePackage" Version="1.2.3" PrivateAssets="compile;contentfiles;build;analyzers" />
</ItemGroup>

Si va a crear un paquete a partir de un archivo nuspec personalizado, en lugar de permitir que NuGet genere uno automáticamente, nuspec debe usar el atributo XML exclude.

<dependencies>
  <group targetFramework=".NETFramework4.8">
    <dependency id="OtherProject" version="3.2.1" exclude="Compile,Build,Analyzers" />
    <dependency id="SomePackage" version="1.2.3" exclude="Compile,Build,Analyzers" />
  </group>
</dependencies>

Hay tres motivos por los que esta es la solución recomendada.

En primer lugar, los ensamblados o paquetes nuevos suelen hacer referencia a ensamblados útiles. Aunque un ensamblado de utilidad podría estar pensado para que solo lo use un único paquete en la actualidad, lo que hace que sea tentador enviar ambos ensamblados en un único paquete, si un segundo paquete quiere usar el ensamblado de utilidad "privado" en el futuro, el ensamblado de utilidad debe moverse a un nuevo paquete y el paquete antiguo debe actualizarse para declararlo como una dependencia. Si no, el paquete de utilidad debe enviarse tanto en el paquete existente como en el nuevo. Si el ensamblado se distribuye en dos paquetes diferentes y un proyecto hace referencia a ambos paquetes, si hay versiones diferentes del ensamblado de utilidad en los dos paquetes, NuGet no podrá ayudar en la administración de versiones.

En segundo lugar, puede haber ocasiones en las que los desarrolladores que usan el paquete quieran usar también las API de las dependencias. Pongamos como ejemplo el paquete Microsoft.ServiceHub.Client, versión 3.0.3078. Si descarga el paquete y comprueba el archivo nuspec, verá que tiene dos paquetes que empiezan por Microsoft.VisualStudio. como dependencias, lo que significa que los necesita en tiempo de ejecución, pero también excluye sus recursos de compilación. Esto significa que los proyectos que usan Microsoft.ServiceHub.Client no tendrán las API de Visual Studio disponibles en IntelliSense o si compilan el proyecto, a menos que el proyecto instale explícitamente esos paquetes. Y esta es la ventaja que tiene una dependencia de paquete con un recurso de exclusión. Los proyectos que usan el paquete, si también quieren usar las dependencias, pueden agregar una referencia al paquete para que las API estén disponibles para sí mismos.

Por último, algunos autores de paquetes se han confundido en el pasado sobre la selección de ensamblados de NuGet para los paquetes que admiten más de una plataforma de destino cuando su paquete también contiene varios ensamblados. Si el ensamblado principal admite distintas plataformas de destino para el ensamblado de utilidad, puede que no sea obvio en qué directorios de lib/ colocar todos los ensamblados. Al separar cada paquete por nombre de ensamblado, es más intuitivo en qué carpetas de lib/ debe ir cada ensamblado. Tenga en cuenta que esto no significa tener los paquetes Package1.net48 y Package1.net6.0. Significa tener lib/net48/Package1.dll y lib/net6.0/Package6.0 en Package1, y lib/netstandard2.0/Package2.dll y lib/net5.0/Package2.dll en Package2. Cuando Nuget restaura un proyecto, realizará de forma independiente la selección de recursos para los dos paquetes.

Tenga en cuenta también que los recursos de inserción o exclusión de dependencias solo se usan en proyectos que usan PackageReference. Cualquier proyecto que instale el paquete mediante packages.config instalará las dependencias y también tendrá sus API disponibles. packages.config solo es compatible con plantillas de proyecto de .NET Framework anteriores de Visual Studio. Los proyectos de estilo SDK, incluso los que tienen .NET Framework como destino, no admiten packages.config y, por lo tanto, admiten recursos de inserción o exclusión de dependencias.

PackageReference y packages.config tienen diferentes características disponibles. Si quiere ofrecer soporte a los consumidores de paquetes que usan PackageReference, packages.configo ambos, cambia cómo debe crear el paquete.

Al tener como destino el paquete MSBuild de NuGet no se permite la inclusión automática de referencias de proyecto en el paquete. Solo aparecerán los proyectos a los que se hace referencia como dependencias de paquetes. Hay un problema en GitHub en el que los miembros de la comunidad han compartido las formas en que lograron este resultado, que normalmente implica usar metadatos del elemento PackagePath de MSBuild para colocar archivos en cualquier lugar del paquete, tal y como se describle en la documentación sobre incluir contenido en un paquete y utlizar SuppressDependenciesWhenPacking para evitar que las referencias del proyecto se transformen en dependencias del paquete. También existen herramientas desarrolladas por la comunidad que se pueden usar como alternativa al paquete oficial de NuGet, que admite esta característica.

Compatibilidad con PackageReference

Cuando un consumidor de paquetes usa PackageReference, NuGet selecciona recursos de compilación y de tiempo de ejecución de forma independiente, tal y como se describió anteriormente.

Los recursos de compilación prefieren ref/<tfm>/*.dll (por ejemplo, ref/net6.0/*.dll), pero si no existe, entonces usarán lib/<tfm>/*.dll (por ejemplo, lib/net6.0/*.dll).

Los recursos de tiempo de ejecución prefieren runtimes/<rid>/lib/<tfm>/*.dll (por ejemplo, runtimes/win11-x64/lib/net6.0/*.dll), pero si no existe, entonces usarán lib/<tfm>/*.dll.

Dado que los ensamblados de ref\<tfm>\ no se usan en tiempo de ejecución, podrían ser ensamblados de solo metadatos para reducir el tamaño del paquete.

Compatibilidad con packages.config

Los proyectos que usan packages.config para administrar paquetes NuGet suelen agregar referencias a todos los ensamblados del directorio lib\<tfm>\. El directorio ref\ se ha agregado para admitir PackageReference, por lo que no se tiene en cuenta al usar packages.config. Para establecer explícitamente a qué ensamblados se hace referencia para los proyectos que usan packages.config, el paquete debe emplear el elemento <references> en el archivo nuspec. Por ejemplo:

<references>
    <group targetFramework="net45">
        <reference file="MyLibrary.dll" />
    </group>
</references>

Los objetivos del paquete MSBuild no admiten el elemento <references>. Consulte la documentaciónsobre el empaquetado mediante un archivo .nuspec al usar el paquete MSBuild.

Nota:

El proyecto packages.config usa un proceso denominado ResolveAssemblyReference para copiar los ensamblados en el directorio de salida bin\<configuration>\. Se copia el ensamblado del proyecto y, luego, el sistema de compilación examina el manifiesto del ensamblado en busca de los ensamblados a los que se hace referencia. Después, copia esos ensamblados y repite el proceso de forma recursiva para todos los ensamblados. Esto significa que si cualquiera de los ensamblados cargados solo por reflexión (Assembly.Load, MEF u otro marco de inserción de dependencias), es posible que no se copie en el directorio de salida bin\<configuration>\ del proyecto a pesar de estar en bin\<tfm>\. Esto también significa que esto solo funciona para ensamblados de .NET, no para código nativo llamado con P/Invoke.

Compatibilidad con PackageReference y packages.config

Importante

Si un paquete contiene el elemento <references> de nuspec y no incluye ensamblados en ref\<tfm>\, NuGet anunciará los ensamblados que aparecen en el elemento <references> nuspec como recursos en tiempo de compilación y ejecución. Esto significa que habrá excepciones en tiempo de ejecución cuando los ensamblados a los que se hace referencia necesiten cargar otro ensamblado en el directorio lib\<tfm>\. Por lo tanto, es importante usar el elemento <references> de nuspec para admitir packages.config, así como para duplicar los ensamblados de la carpeta ref/ para obtener compatibilidad con PackageReference. No es necesario usar la carpeta del paquete runtimes/, ya que se agregó a la sección anterior para completarse.

Ejemplo

Mi paquete contendrá tres ensamblados (MyLib.dll, MyHelpers.dll y MyUtilities.dll), que están destinados a .NET Framework 4.7.2. MyUtilities.dll contiene clases diseñadas para que las usen únicamente los otros dos ensamblados, por lo que no quiero que esas clases estén disponibles en IntelliSense o en tiempo de compilación para los proyectos que usen mi paquete. Mi archivo nuspec debe contener los siguientes elementos XML:

<references>
    <group targetFramework="net472">
        <reference file="MyLib.dll" />
        <reference file="MyHelpers.dll" />
    </group>
</references>

Tengo que asegurarme de que el contenido del paquete sea el siguiente:

lib\net472\MyLib.dll
lib\net472\MyHelpers.dll
lib\net472\MyUtilities.dll
ref\net472\MyLib.dll
ref\net472\MyHelpers.dll