El metapaquete Microsoft.AspNetCore.All

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 runtime 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 storepor 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 runtime store se encuentra en %PROGRAMFILES%/dotnet/store:

runtime 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

Comparación carpetas publicación

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:

autofac publicado

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)

store propia

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 <TargetManifestFiles> para incluir ficheros de manifiesto directamente en el csproj. De ese modo evitas tener que usar el parámetro “–manifest” en el comando dotnet publish. Efectivamente se llama TargetManifestFiles en plural porque puedes poner varios (separados por punto y coma):

<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 storeSon 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!

2 comentarios sobre “El metapaquete Microsoft.AspNetCore.All”

  1. Dudo si es pertinente mi comentario, más bien consulta.
    Aclaro que estoy haciendo mis primeras armas en VS2017 ASP.NET Core. Vengo de la prehistoria del desarrollo.
    Me fue bastante bien con la versión 1.0.0 de Core.
    Pero cuando quise volver a crear un proyecto falló en la instalación de paquetes Nuget.
    Tuve una profusión de mensajes:

    Error NU1202 El paquete Microsoft.EntityFrameworkCore.Tools 2.0.1 no es compatible con netcoreapp1.1 (.NETCoreApp,Version=v1.1). El paquete Microsoft.EntityFrameworkCore.Tools 2.0.1 admite: netstandard2.0 (.NETStandard,Version=v2.0)

    ¿Esto se resuelve con el metapaquete?

    1. Buenas!
      El error que te da es porque estás usando el paquete Microsoft.EntityFrameworkCore.Tools en su versión 2.0.1 con una aplicación NetCore1x y ese paquete requiere NetCore2 (de hecho requiere NetStandard2, pero NetCore2 lo implementa, mientras que NetCore1x no).
      En resúmen: si usas netcore1x usa la última versión 1x del paquete. Otra opción es que cambies el proyecto para que use netcoreapp2.0 y usar NetCore2.

      Para usar netcoreapp2.0 abre el csproj y busca el y dale el valor netcoreapp2.0 (netcoreapp2.0)

      Si hubiera habido un metapaquete en NetCore1x no tendrías ese problema (ya que el metapaquete evitaría que usaras una versión no soportada), así que en parte sí que estos problemas los resuelve el uso del metapaquete.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *