Aplicaciones de fichero único en .NET 5.0

El modelo de implementación de aplicaciones basado en DLL es tan antiguo como el propio Windows, y se remonta incluso más allá. Como es sabido, la idea subyacente es que ciertas partes del código no se carguen en memoria hasta que son necesarias, reduciendo así el perfil de memoria de la aplicación. También hay motivos relacionados con el mantenimiento y la arquitectura: dividir los componentes de la aplicación en ficheros separados facilita las actualizaciones del software.

Sin embargo .NET 5.0 introduce muchos cambios en los paradigmas habituales de arquitectura e implementación. Uno de ellos consiste en la posibilidad de compilar la aplicación en un fichero único que incluya todos los componentes de la aplicación. Veamos cuáles son los elementos fundamentales de esta tecnología.

Implementación independiente del marco

La base de esta funcionalidad se halla en una característica presente en .NET Core desde la primera implementación. Se trata de la posibilidad de crear una implantación independiente del marco. Esta característica permite publicar la aplicación incluyendo todas las DLL que necesita para funcionar, y cuando decimos todas queremos decir también las DLL del propio .NET Core. Este tipo de implementación copiará junto al ejecutable de la aplicación una copia de todas las DLL de .NET Core necesarias para ejecutarla.

Desde luego esto produce una salida muy voluminosa, con gran cantidad de ficheros. Por lo general, si estamos creando una aplicación web, será más práctico realizar una implementación dependiente del marco, lo que significa que esperamos que en el sistema operativo anfitrión se halle presente la versión de .NET Core adecuada para ejecutar la aplicación. Esto produce una publicación mucho más pequeña, ya que solo incluye los archivos compilados propios de la aplicación.

La opción de la implantación independiente del marco nos permite ejecutar la aplicación en cualquier equipo, tenga instalado .NET Core o no lo tenga. La publicación contiene todo lo necesario para ejecutarse, e incluso en un mismo equipo pueden ejecutarse de manera simultánea diversas aplicaciones creadas con versiones diferentes, ya que cada una carga en memoria su propia versión de .NET Core.

Publicación de fichero único

A partir de .NET Core 3 tenemos la opción de realizar una implementación de fichero único, tanto si nuestra implementación es independiente del marco como si no lo es.

Single File .NET Core

Si la implementación que estamos realizando es dependiente del marco en nuestro fichero único solo se encontrarán los archivos de la aplicación y de los componentes de terceros que utilice. Si es independiente incluirá todos los ficheros de .NET Core (o de .NET 5.0).

Desde luego, esta última opción producirá un archivo muy grande. Para reducir el tamaño, .NET 5.0 dispone de la opción Quitar los ensamblados no usados. Esta característica permitirá eliminar del fichero resultante aquellos ensamblados de .NET 5.0 que no estén referenciados ni directa ni indirectamente por la aplicación, reduciendo considerablemente el tamaño de fichero final.

Por último, es necesario señalar que, a pesar de que la opción de implementación en fichero único ya estaba presente en .NET Core 3.x, la forma de implementarla varía considerablemente en .NET 5.0. En la edición 3.x los ficheros se descomprimían en un directorio temporal y se ejecutaban desde allí. En la edición de .NET 5.0 el programa se ejecuta directamente desde el fichero único, lo cual acelera el tiempo de inicio.

Riesgos

No todos los escenarios son apropiados para esta técnica. En particular, el uso de Reflection para cargar dinámicamente bibliotecas de enlace dinámico o cualquier otro fichero fallará, ya que en el directorio de la aplicación no habrá más que un único fichero que lo contendrá todo, y (al menos en esta versión de .NET), no podremos acceder a “partes” de ese fichero mediante Reflection.

Si a pesar de todo queremos seguir empleando esta técnica, podemos determinar que ciertos archivos no se incluyen dentro del fichero único en las opciones de metadatos:

<ItemGroup>
  <Content Update="MiComponente.dll">
    <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
    <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
  </Content>
</ItemGroup>