Nota: Este post ha sido importado de mi blog de geeks.ms. Es posible que algo no se vea del todo "correctamente". En cualquier caso puedes acceder a la versión original aquí
Todos estamos acostumbrados a usar los paquetes de NuGet en nuestros desarrollos. Pero a raíz de Net Core 2.0, apareció el concepto de metapaquete. Qué es exactamente un metapaquete y por qué existen?
La respuesta rápida es que un metapaquete de NuGet es simplemente un paquete que no incluye ningún ensamblado, solo referencia a otros paquetes. Es, en definitiva, un mecanismo para “agrupar” paquetes de NuGet bajo un mismo número de version.
Pero de todos los metapaquetes existentes, el más curioso es sin duda el metapaquete de Asp.Net Core, que contiene referencias a todos los paquetes que conforman ASP.NET Core. Todos es básicamente todos, incluyendo Razor, proveedores de autenticación externa y también Entity Framework. Eso significa que podemos empezar cualquier desarrollo con ASP.NET Core usando tan solo una sola referencia en nuestro csproj:
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.3" />
No es necesario que incluyamos la pléyade paquetes que conforman ASP.NET Core y Net Core. Este _metapaquete _los incluye todos por nosotros bajo un solo número de version. Con eso solucionamos un problema típico: actualizar versiones de paquetes NuGet en ASP.NET Core era un infierno, especialmente si uno lo hacía editando el csproj a mano. Saber exactamente cual era la última versión de todos los paquetes no era sencillo. Ahora, gracias al metapaquete nos basta con saber cual es la última version de éste para tener todos los paquetes base de ASP.NET Core actualizados. Sencillo, rápido y eficaz.
Pero… ¿eso no es un paso atrás?
Una de las novedades de Net Core (y ASP.NET Core) era, precisamente, que estaba segmentado en múltiples paquetes NuGet. Ya no teníamos la dependencia al mega-ensamblado System.Web y solo instalábamos lo que necesitábamos. A cambio hemos visto que eso nos generaba unos csproj relativamente grandes, con muchas entradas PackageReference para incluir todos los paquetes NuGet necesarios.
De acuerdo, es cierto, tener muchos PackageReference hace más complejo el actualizar las versiones pero permite que al publicar mi aplicación se publiquen solo los paquetes NuGet que usemos. Si no voy a usar Entity Framwork no quiero tener que publicar todos sus paquetes junto con mi aplicación. Esa es, precisamente, la ventaja de segmentar el framework en varios paquetes: publicar solo lo que se use. Pero… si resulta que la única referencia de mi proyecto es un metapaquete que, cual anillo del poder, los incluye a todos… al publicar mi aplicación, ¿qué se publicará?
Está claro que nos hace falta una pieza más y esa pieza tiene nombre: la default runtime store. ¿Qué narices es la default r__untime store? Pues simplemente, que ahora al instalar el runtime de netcore se preinstalan todos los paquetes oficiales de Microsoft. Esos paquetes están disponibles en una ubicación central (esa _default _runtime store) por lo que no es necesario que se publiquen junto con cualquier aplicación. Eso hace que los binarios no sean más grandes (de hecho, serán más pequeños que el equivalente de NetCore 1.1).
La default r__untime store se encuentra en %PROGRAMFILES%/dotnet/store:
Para comparar vamos a ver la diferencia entre publicar la plantilla por defecto de ASP.NET Core 1.1 y 2.0 (en ambos casos uso VS2017 con File->New Project->ASP.NET Core Web Application ->Web Application (Model View Controller) con la autenticación de usuarios locales, en un caso seleccionando .NET Core 2.0 y en el otro .NET Core 1.1).
Esas son todas las referencias incluidas de serie en el proyecto de NetCore 1.1:
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.0" /> <PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="1.1.2" /> <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="1.1.2" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="1.1.2" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" /> <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.2" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" PrivateAssets="All" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.2" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.2" PrivateAssets="All" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1" PrivateAssets="All" /> <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="1.1.2" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.2" /> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="1.1.1" PrivateAssets="All" /> <PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="1.1.2" />
Por otro lado en el proyecto de NetCore 2.0 solo tenemos el metapaquete y dos referencias adicionales (la CLI de EF y el paquete para generar código usado por EF). Ninguna de esas dos referencias adicionales se necesita en ejecución.
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.1" PrivateAssets="All" /> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.1" PrivateAssets="All" />
Luego publico con dotnet publish -c release ambas versiones. La carpeta de la publicación de NetCore 1.1 contiene 98 ensamblados del sistema (sin incluir el de la propia aplicación), mientras que la carpeta de NetCore 2.0 no contiene ningún ensamblado
Por supuesto, la carpeta de publicación de NetCore 2.0 confía, precisamente, en que todos los ensamblados estarán disponibles… gracias a la default _runtime store. Por supuesto los ensamblados contenidos en la default runtime store se alinean con los del metapaquete Microsoft.AspNetCore.All._
Por lo tanto: usando el metapaquete de Microsoft.AspNetCore.All no estamos yendo “hacia atrás”, en el sentido de que no pasamos a depender de “un único paquete”. Nuestro código sigue, realmente, dependiendo de muchos paquetes NuGet, solo que esos ya vienen preinstalados en cualquier sistema que tenga NetCore 2.0.
Ahora si añades un paquete cualquiera, como Autofac, observarás como este paquete si que se publica junto a tu aplicación:
Creando tus propias runtime store
El concepto de runtime store es extensible y de hecho te puedes crear las tuyas propias. Imagina que tienes un paquete NuGet que usas constantemente. Puede ser Autofac, puede ser Moq, puede ser un paquete propio. Da igual. El tema está en que en los ordenadores de destino este paquete va a estar instalado en la _runtime store _ y por lo tanto no quieres incluirla en cada aplicación publicada. Vamos a ver como puedes hacer esto.
Para ello necesitas un manifiesto de package store. P. ej. aquí tienes un ejemplo de manifiesto de package store que incluyte Autofac:
<Project Sdk="Microsoft.NET.Sdk"> <ItemGroup> <PackageReference Include="Autofac" Version="4.6.2" /> </ItemGroup> </Project>
Como puedes observar el formato del fichero de manifiesto es, de hecho, compatible con el csproj. En mi caso he llamado al fichero “autofac_manifest.xml”.
Ahora puedo usar el comando dotnet store para crear una runtime store:
dotnet store -m autofac_manifest.xml --framework netcoreapp2.0 --framework-version 2.0.0 --runtime win10-x64
Eso me creará una runtime store_ _(por defecto ubicada en %USERPROFILE%.dotnet\store, se puede modificar usando –output)
Con eso creamos la _runtime store _en la máquina. Es interesante ver que, por supuesto, dotnet store resuelve dependencias y si un paquete depende de otro, al final tu store tendrá todos los paquetes (los indicados en el manifiesto y sus dependencias).
El siguiente paso, es publicar un proyecto contra un manifiesto de runtime store._ _Cuando hemos creado la runtime store nos ha creado un fichero “artifact.xml”. Este fichero es el manifiesto de la runtime store que podemos pasarle a dotnet publish.
dotnet publish -c release -o out --manifest C:\Users\etoma\.dotnet\store\x64\netcoreapp2.0\artifact.xml
Si lo pruebas verás como a pesar de que nuestro fichero csproj tiene una referencia a Autofac, ahora la DLL de Autofac no se publica.
Puedes usar la etiqueta
<PropertyGroup> <TargetManifestFiles>path/a/manifest1.xml;path/a/manifest2.xml</TargetManifestFiles> </PropertyGroup>
De este modo puedes tener los distintos “artifact.xml” publicados en tu repositorio de código fuente y directamente usarlos en los csproj.
Publicar sin incluir la default runtime store
Es posible que quieras publicar “a la antigua usanza”, es decir con todas las dependencias de tu proyecto. Para ello te basta con usar la propiedad PublishWithAspNetCoreTargetManifest del csproj y ponerla a false:
<PropertyGroup> <PublishWithAspNetCoreTargetManifest>false</PublishWithAspNetCoreTargetManifest> </PropertyGroup>
Con eso indicas que no quieres publicar contra la default runtime store, por lo que ahora, al publicar se incluirán todas las referencias de tu aplicación (por cierto, en mi caso, usando la plantilla por defecto son 192 ensamblados, ¡ahí es nada!). Es importante notar que puedes usar PublishWithAspNetCoreTargetManifest a la vez que TargetManifestFiles (o la opción –manifest en dotnet publish) y así darse el caso de publicar los ensamblados por defecto de ASP.NET Core pero no otros que tu quieras. Hay una flexibilidad total.
Despliegues FDD vs despliegues SCD
Este apartado es simplemente aclaratorio. No tiene que ver directamente con el uso de metapaquetes pero ya que hemos tocado el tema de publicaciones, considero interesante añadirlo. Llamamos un despliegue FDD (Framework-dependent deployment) al despliegue que contiene solo tu aplicación y las dependencias de terceros, pero no las de NetCore. Un despliegue FDD usará la versión de NetCore instalada en la máquina de destino. La ventaja principal es que tu aplicación ocupa menos, la desventaja es que el ordenador de destino debe tener instalado la versión de NetCore contra la que se compiló tu aplicación o una posterior (eso podría llegar a causar problemas en el caso de que una versión posterior de NetCore rompiese la compatibilidad). **No confundas un despliegue FDD con usar la default runtime store. **Son dos cosas distintas:
- Puedes tener un despliegue FDD con todos los ensamblados (sin usar la default runtime store)
- Puedes tener un despliegue FDD con solo tus ensamblados (usando la default runtime store)
En NetCore 1.x por defecto tenemos el primer caso, y en NetCore 2, el segundo. Pero en ambos casos usamos FDD.
El segundo tipo de despliegue es SCD (Self-contained deployment) que es cuando desplegamos NetCore junto con nuestra aplicación. De este modo nos aseguramos que nuestra aplicación funcione en cualquier máquina, ya que viene con “su propio NetCore”. El único requisito es que la máquina tenga las dependencias nativas de NetCore instaladas (SCD incluye NetCore pero no sus dependencias nativas).
A diferencia de una publicación FDD donde todos los ensamblados publicados son en formato PE y compatibles con cualquier sistema (los mismos binarios te funcionan en Windows, Mac y Linux), una publicación SCD debe ser para una plataforma en concreto (ya que debe incluirse la versión de NetCore para la plataforma), por lo que debemos tener tantas publicaciones como plataformas queramos soportar.
Bueno… espero que eso os haya ayudado a aclarar las dudas que, quizá, teníais sobre este “mágico” paquete de NuGet que lo incluye todo… ¡pero realmente no incluye nada!