En los proyectos tradicionales de .NET Framework, el directorio de salida de compilación suele saturarse con múltiples archivos DLL de dependencias, lo que dificulta la identificación rápida del ejecutable principal (.exe) y los archivos de configuración. Para mantener una estructura de directorios limpia y organizada, es una práctica común trasladar estos ensamblados dependientes a una subcarpeta dedicada.
Aunque existen paquetes de NuGet como PrettyBin que automatizan este proceso mediante scripts de PowerShell, comprender y aplicar la configuración subyacente de MSBuild y del CLR (Common Language Runtime) ofrece una solución más robusta, personalizable y compatible con los formatos modernos de gestión de paquetes.
Implementación mediante MSBuild
El núcleo de esta solución reside en interceptar el proceso de compilación para mover los archivos generados. En lugar de utilizar el evento AfterBuild estándar, podemos definir un Target personalizado que se ejecute después de la compilación, asegurando que el ensamblado principal del proyecto no sea movido accidentalmente.
Añada el siguiente bloque XML al final de su archivo de proyecto (.csproj), justo antes de la etiqueta de cierre </Project>:
<Target Name="RelocateDependencies" AfterTargets="Build">
<PropertyGroup>
<DependencyDir>$(OutputPath)dependencies\</DependencyDir>
</PropertyGroup>
<!-- Crear el directorio si no existe -->
<MakeDir Directories="$(DependencyDir)" Condition="!Exists('$(DependencyDir)')" />
<!-- Seleccionar archivos a mover, excluyendo el ensamblado principal -->
<ItemGroup>
<FilesToMove Include="$(OutputPath)*.dll;$(OutputPath)*.pdb;$(OutputPath)*.xml"
Exclude="$(OutputPath)$(TargetFileName);$(OutputPath)$(TargetName).pdb" />
</ItemGroup>
<!-- Ejecutar el movimiento -->
<Move SourceFiles="@(FilesToMove)"
DestinationFolder="$(DependencyDir)"
OverwriteReadOnlyFiles="true" />
</Target>
Este script de MSBuild realiza las siguientes operaciones:
- Define una propiedad
DependencyDirque apunta a una subcarpeta llamadadependenciesdentro del directorio de salida. - Utiliza la tarea
MakeDirpara garantizar que la carpeta de destino exista antes de intentar mover archivos. - Crea un grupo de elementos (
ItemGroup) que incluye todos los archivos.dll,.pdby.xml, pero excluye explícitamente el archivo de salida principal del proyecto ($(TargetFileName)) y su archivo de símbolos asociado. - Ejecuta la tarea
Movepara trasladar los archivos seleccionados, sobrescribiendo cualquier archivo de solo lectura existente.
Configuración del CLR para la Resolución de Ensamblados
Mover los archivos físicamente no es suficiente; el CLR debe saber dónde buscarlos en tiempo de ejecución. Por defecto, el AppDomain solo busca en el directorio base de la aplicación. Para extender esta búsqueda a subdirectorios, debemos modificar el archivo de configuración de la aplicación (App.config o Web.config).
Inserte la siguiente configuración dentro de la sección <runtime>:
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="dependencies" />
</assemblyBinding>
</runtime>
</configuration>
El elemento <probing> instruye al mecanismo de sondeo del CLR para que busque ensamblados dependientes en las rutas especificadas en el atributo privatePath. Si necesita múltiples carpetas, puede separarlas con punto y coma (por ejemplo, privatePath="dependencies;libs;plugins").
Compatibilidad con Formatos de Gestión de Paquetes
Es crucial entender cómo interactúa esta técnica con los diferentes formatos de gestión de paquetes de NuGet:
- packages.config: Los paquetes NuGet tradicionales pueden incluir scripts
install.ps1que modifican automáticamente el archivo.csprojy elApp.configal instalarse. Herramientas de terceros dependen de este mecanismo para inyectar configuraciones. - PackageReference: Este es el formato moderno y recomendado, utilizado por defecto en .NET Core, .NET Standard y las versiones recientes de .NET Framework. En este modelo, NuGet no ejecuta scripts de PowerShell durente la instalación. Por lo tanto, los paquetes que dependen de
install.ps1fallarán silenciosamente. La implementación manual de MSBuild descrita anteriormente es totalmente compatible conPackageReference, ya que no depende de scripts de instalación de paquetes, sino de la configuración nativa del proyecto.
En ecosistemas modernos como .NET Core o .NET 5+, la gestión de dependencias y el modelo de publicación (como los ejecutables de archivo único o Single-file executables) manejan la resolución de ensamblados de manera diferente, haciendo que esta técnica de reubicación de carpetas sea innecesaria y, en algunos casos, incompatible con el host de ejecución nativo.