Microsoft acaba de presentar, en el marco del evento .NET Conf 2019, la nueva versión de .NET Core y el conjunto de tecnologías y frameworks relacionados.
Si bien para ASP.NET Core no se trata de una versión especialmente revolucionaria, sí que viene acompañada de algunos breaking changes y novedades que vale la pena comentar.
Lo que vamos a ver no es una lista exhaustiva de los cambios introducidos con esta versión (¡para eso está la documentación oficial!), pero sí aquellos aspectos que más me han llamado la atención:
- Simplificación del archivo de proyecto
.csproj
- Uso del host genérico
- Endpoint routing
- Cambios en el registro de servicios de MVC
- Bye bye, JSON.NET!
- Bye bye, target .NET Framework!
- Limpieza de
Microsoft.AspNetCore.App
- Compilación de vistas
- Soporte gRPC
- Blazor Server-Side
- Otras mejoras
Simplificación del archivo de proyecto .csproj
El SDK utilizado en los archivos de proyecto, Microsoft.NET.Sdk.Web
, incluye ahora características por defecto que hacen innecesaria la especificación a nivel del .csproj
de aspectos que anteriormente eran necesarios, como la referencia a Microsoft.AspNetCore.App
y otros paquetes necesarios, o la habilitación del hosting in-process de IIS.
De esta forma, los archivos de proyecto se han simplificado aún más. Como muestra, este era el contenido de una aplicación ASP.NET Core MVC típica en ASP.NET Core 2.2:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
</ItemGroup>
</Project>
Y así es como queda en ASP.NET Core 3.0, básicamente reducido a la indicación del Target Framework Moniker (TFM):
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
</Project>
Host genérico
En versiones anteriores de ASP.NET Core, el arranque de la aplicación web estaba ligado irremediablemente a un WebHost
. Eso hacía que muchas de las funcionalidades proporcionadas por el marco de trabajo fueran difícilmente utilizables fuera de ASP.NET Core.
Esto se ponía de manifiesto muy claramente si echábamos un vistazo al archivo Program.cs
, donde directamente estábamos usando un IWebHostBuilder
para crear el WebHost
que lanzaba la aplicación:
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
En ASP.NET Core 3.0, esto ha mejorado bastante gracias a la puesta en valor del host genérico, un componente que ya se utilizaba para entornos no web en versiones anteriores del framework, y que ahora pasa a formar parte también de las aplicaciones ASP.NET Core, unificando así determinados aspectos que antes quedaban algo dispersos.
Este host actúa como un entorno de ejecución para aplicaciones de (virtualmente) cualquier tipo, proporcionándoles servicios básicos como el sistema de configuración, logging, lifetime management, o los servicios de inyección de dependencias. La idea es que estos servicios sean los mismos y se configuren de la misma forma, estemos programando una aplicación ASP.NET Core o un servicio para Windows.
Observad ahora, en ASP.NET Core 3.0, cómo cambia la cosa. En el Program.cs
simplemente construimos y configuramos un host genérico, que "aderezamos" con particularidades de la web a través del extensor ConfigureWebHostDefaults()
:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Endpoint routing
Este es uno de los cambios de mayor calado en esta nueva versión del framework. El sistema de routing de ASP.NET Core ha sido reestructurado para convertirlo en una solución mucho más eficiente, potente y flexible, y desligado de componentes o tecnologías con un nivel de abstracción superior como MVC o SignalR.
En ASP.NET Core 3.0, se considera que un endpoint es cualquier cosa capaz de procesar peticiones. Puede ser un handler implementado en una lambda, una acción MVC, un hub SignalR, una página Razor o un servicio gRPC; desde el punto de vista del routing da lo mismo: es sólo un procesador de peticiones al que se puede llegar mediante una ruta.
Estos endpoints son registrados de forma global durante la inicialización de la aplicación utilizando métodos de configuración que facilitan bastante la tarea. Al finalizar, el sistema tendrá una visión clara de qué patrones de rutas soporta la aplicación y cómo y dónde pueden ser procesadas.
Por ejemplo, en el siguiente código, perteneciente al método Configure()
de la clase Startup
, vemos cómo registrar un handler inline en una lambda, endpoints para todas las acciones de una aplicación MVC, de las páginas Razor que tengamos en la carpeta /Pages
, y de un hub SignalR:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseRouting();
...
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/hello", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
endpoints.MapHub<ChatHub>("/myhub");
});
}
Por tanto, ya no podremos utilizar el conocido UseMvc()
para añadir el framework MVC al pipeline. Ahora, al pipeline añadimos el middleware encargado de ejecutar los endpoints, y en la configuración de éste es donde mapeamos las acciones.
Otros aspecto interesante, que ya he dejado entrever en el bloque de código anterior, es que, la tarea de decidir qué endpoint será el encargado de procesar cada petición entrante está separada de la tarea de ejecutarlo. Existe un middleware específico para cada una de estas tareas:
public void Configure(IApplicationBuilder app)
{
...
// En este punto se decide qué endpoint procesará la petición:
app.UseRouting();
... // Aquí irían otros middlewares
// En este punto se ejecuta realmente el endpoint seleccionado:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Esta separación de responsabilidades proporciona unas posibilidades que hasta ahora no teníamos: una vez se ha seleccionado el endpoint que procesará una petición, pueden existir middlewares intermedios que examinen al candidato y tomen decisiones antes de ser ejecutado. Por ejemplo, el nuevo sistema de autorización funciona de esta forma: analiza si el endpoint seleccionado puede ser ejecutado por el usuario actual, y, en caso contrario, fuerza una redirección hacia la URL que le hayamos indicado.
Fijaos que esto hace posible que la gestión de algunos aspectos transversales, como la autorización, CORS y otros, sea llevada a cabo por completo desde la infraestructura de ASP.NET Core y no desde implementaciones específicas existentes en el interior de frameworks como MVC.
Cambios en el registro de servicios de MVC
Profundizando la línea de modularidad que definió desde sus principios todo lo relacionado con ASP.NET Core, en la versión 3.0 encontramos nuevos extensores para registrar los servicios de MVC en el sistema de inyección de dependencias, de forma más granular.
Hasta ahora, usábamos algo como lo siguiente:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
Con esa simple llamada, registrábamos todo lo necesario para que funcionara el framework MVC (controladores, vistas) y Razor Pages. Sin embargo, esto era un exceso para escenarios más específicos, como aplicaciones que actuaban exclusivamente como APIs, o sistemas que utilizaban MVC y no Razor Pages, o viceversa.
Para ello, se han introducido los siguientes extensores de IServiceCollection
:
AddControllers()
, para registrar los componentes necesarios para ejecutar exclusivamente acciones, por ejemplo en una aplicación API donde no vamos a necesitar vistas MVC ni Razor Pages.
AddControllersWithViews()
, registrará los componentes necesarios para ejecutar acciones, pero también para poder procesar vistas Razor.
AddRazorPages()
, por último, si queremos registrar los servicios requeridos para ejecutar Razor Pages.
Por tanto, el método ConfigureServices()
de una aplicación típica MVC podría quedar como sigue:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersAndViews();
}
Nota: AddMvc()
sigue existiendo, y equivale a llamar de forma consecutiva a AddControllersWithViews()
y AddRazorPages()
, siendo así compatible con versiones anteriores de ASP.NET Core.
¡Bye bye, JSON.NET!
Pues sí, la biblioteca de serialización/deserialización de JSON más popular para aplicaciones .NET pasa a ser un componente opcional, sólo requerido por razones de retrocompatibilidad o cumplimiento de dependencias de otros componentes.
Pero no nos quedamos huérfanos 😉 A partir de ahora, por defecto ASP.NET Core utilizará internamente el nuevo paquete NuGet System.Text.Json
, que de serie formará parte de Microsoft.AspNetCore.App
.
El uso de este nuevo componente aporta grandes ventajas:
- Dado que ahora las herramientas de serialización y deserialización JSON serán proporcionadas por la plataforma, se ha podido eliminar la dependencia de JSON.NET en muchos componentes del framework y de terceras partes. Esto ha sido, y aún es, fuente de numerosos problemas e incompatibilidades entre versiones (si lo has sufrido, sabrás de lo que hablo).
- Aumento del rendimiento, entre 1.5 y 5 veces superior a JSON.NET, entre otras cosas gracias al uso intensivo de recientes incorporaciones al framework, como los tipos
Span<T>
y relacionados.
Y en cuanto al uso, tampoco es que cambie demasiado. Así es como serializaríamos un objeto con la nueva versión:
string serializedInvoice = JsonSerializer.Serialize(invoice);
¡Bye bye, .NET Framework!
Las primeras versiones de ASP.NET Core podían correr tanto sobre .NET Core como .NET Framework. Estratégicamente era bastante apropiado, pues el ecosistema de .NET Core (bibliotecas, paquetes NuGet...) estaba bastante verde, y la única forma de conseguir que los desarrolladores fueran introduciendo el nuevo framework era proporcionarles esta posibilidad.
Al mismo tiempo, .NET Standard fue estableciendo las directrices con las que debían alinearse tanto los componentes de terceros como los propios marcos de trabajo de Microsoft para ser interoperables, llevándonos al momento actual. Hoy, la mayoría de bibliotecas, o al menos las más populares, implementan .NET Standard y son utilizables directamente desde .NET Core, restando sentido a la posibilidad de ejecutar ASP.NET Core sobre .NET Framework.
Otro motivo de la retirada del soporte para el viejo marco de trabajo es que éste no va a evolucionar más. De hecho, .NET Framework 4.8, que es la última versión que aparecerá, ya no es compatible con la última versión de .NET Standard 2.1, por lo que podríamos considerar que, oficialmente, se ha quedado por detrás.
Limpieza de Microsoft.AspNetCore.App
En esta nueva versión han aprovechado para limpiar un poco Microsoft.AspNetCore.App
, que incluía ensamblados que nada tenían que ver con ASP.NET Core.
Este cambio no supone pérdida de funcionalidad alguna, pues los ensamblados retirados de este shared framework aún siguen estando disponibles como paquetes NuGet independientes. Por si te interesa verlo con más detalle, la lista completa de ensamblados eliminados de Microsoft.AspNetCore.App
está en este issue de GitHub.
Compilación de vistas
En esta versión de ASP.NET Core, las vistas son compiladas cuando debe ser: en tiempo de compilación. Los paquetes de publicación incluyen exclusivamente los ensamblados resultado de dicho proceso, y no las vistas o páginas Razor (archivos .cshtml
). Hasta aquí es igual que en ASP.NET Core 2.2.
Lo que cambia es que la posibilidad de editar y compilar las vistas Razor en tiempo de ejecución es un opt-in que debemos activar expresamente durante el registro de servicios:
services
.AddControllersWithViews()
.AddRazorRuntimeCompilation();
Obviamente, aparte es necesario hacer que los archivos de vistas o páginas se incluyan al publicar, para lo cual será necesario introducir algunos cambios en el archivo de proyecto .csproj
.
Soporte gRPC
ASP.NET Core incorpora por primera vez soporte para el estándar gRPC, tanto a nivel de framework como de tooling. Tendremos soporte para la edición de archivos .proto
, generación de clientes, realización de mapeos de endpoints a handlers gRPC, y todo lo que necesitamos para crear clientes o servidores de este tipo de servicios.
gRPC, como sabréis, se trata de un marco de trabajo para la implementación, tanto en cliente como en servidor, de llamadas entre sistemas remotos (RPC) basado en tecnologías recientes como el transporte HTTP/2 y la serialización Protobuf.
¿Y qué ventajas tienen los servicios gRPC frente a los tradicionales APIs HTTP? Pues la primera, el rendimiento: al utilizar serialización binaria y HTTP/2, los paquetes de datos transmitidos pueden ser mucho menores. También, dado que los servicios de definen en archivos .proto
, existe un contrato explícito que los clientes pueden utilizar para generar código de acceso.
Como punto negativo, lo que perdemos es la posibilidad de leer los mensajes directamente, como hacíamos con JSON, y su uso en algunos escenarios (por ejemplo, los browsers no soportan gRPC de forma nativa).
Puedes ver otras ventajas y desventajas en esta página la documentación oficial.
Como curiosidad, esta es la pinta que tiene el código cliente de un servicio gRPC:
static async Task Main(string[] args)
{
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Invoice.GreeterClient(channel);
var reply = await client.SayHelloAsync(
new HelloRequest { Name = "GreeterClient" }
);
Console.WriteLine("Greeting: " + reply.Message);
}
Blazor server-side
Para los que no estéis al tanto, Blazor es un framework de desarrollo frontend, con un enfoque bastante novedoso y sorprendente. Comenzó como un proyecto experimental, una serie de prototipos que fueron evolucionando hasta pasar a ser un proyecto oficial hace algunos meses.
El objetivo de Blazor es conseguir que los desarrolladores puedan crear aplicaciones SPA completas utilizando exclusivamente C#.
Inicialmente, se planteó llevando al browser un runtime de .NET basado en WebAssembly, que ejecutaría el ensamblado resultante de compilar el lado cliente del proyecto. Conforme el proyecto evolucionaba, se añadió también la posibilidad de ejecutar esta lógica en el servidor (Blazor Server), manteniendo una conexión SignalR para el intercambio de eventos, cambios en el UI o llamadas realizadas desde script.
Pues bien, con la nueva oleada de tecnologías Core 3.0 se presenta la primera versión oficial de Blazor, aunque, de momento sólo en uno de sus sabores: Blazor Server-side.
Otras mejoras
Aparte de las vistas anteriormente, citamos otras mejoras en el framework, algunas de ellas derivadas directamente de las novedades introducidas en .NET Core 3:
- La adopción de .NET Standard 2.1.
- Introducción de soporte y plantillas para la implementación de worker services.
- Mejoras en rendimiento gracias a la introducción de mejoras como platform dependent intrinsics, tiered compilation por defecto, ready to run images y muchas otras.
- Mejoras en compilación, linkado y publicación, como el trimming de ensamblados, la copia de dependencias en compilacion, o publicación en un único archivo.
- Si te gustaron las global tools de .NET Core, ahora también llegan las local tools. Básicamente lo mismo, pero ceñidas a un único directorio o proyecto.
- La esperada llegada de C# 8, y sus interesantes novedades, destacando la implementación por defecto de interfaces o los nuevos tipos referencia anulables, que seguro que cambiarán nuestra forma de desarrollar con este lenguaje.
Por supuesto, en campusMVP llevamos varios meses trabajando en la actualización de nuestro mítico curso de ASP.NET Core por José María Aguilar y coincidiendo con el lanzamiento oficial de .NET Core 3, hemos lanzado la nueva versión del curso. ¡No te lo pierdas para estar a la última!
Y para más información, aquí os dejo algunos enlaces:
¡Que lo disfrutéis!