Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

18 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
Mostrando entradas con la etiqueta .net9. Mostrar todas las entradas
Mostrando entradas con la etiqueta .net9. Mostrar todas las entradas
martes, 19 de noviembre de 2024
Nuevos métodos LINQ en .NET 9

Me encanta LINQ. Bueno, ciertamente la sintaxis de consultas integrada en el lenguaje no la uso desde hace muchísimos años, pero los métodos de extensión que proporciona son una maravilla para recorrer y operar sobre colecciones de datos en memoria o almacenados en sistemas externos.

LINQ ha sido uno de los objetivos de .NET 9. Además de mejorarlo internamente y optimizar su rendimiento (en algunos casos, considerablemente), se han incluido tres nuevos métodos, CountBy(), AggregateBy() e Index(), que amplían las posibilidades que teníamos hasta el momento, simplificando la escritura de código y aumentando su legibilidad en algunos escenarios.

Vamos a verlos en detalle.

martes, 12 de noviembre de 2024
Un armario simulando ser la caché de nivel 1, con usuarios accediendo a ella. También hay camiones transportando contenidos al nivel 2

Hace unas semanas estuvimos echando un vistazo a HybridCache, la alternativa de .NET 9 al sistema de caché distribuida que apareció con .NET Core años atrás. Como vimos entonces, HybridCache aporta una serie de ventajas respecto a su predecesor, como la protección contra estampidas, la caché multinivel, invalidación por etiquetas, y otras mejoras.

Sin embargo, también introduce algunos inconvenientes que debemos tener en cuenta si queremos sacarle el máximo partido. En esta ocasión, nos centraremos en la penalización debido a excesiva deserialización de objetos de la caché local, un detalle que puede pasar desapercibido pero que puede tener un impacto significativo en el rendimiento de nuestra aplicación.

martes, 5 de noviembre de 2024
Ordenador con Tareas ejecutándose en paralelo

La clase Task de .NET dispone de algunos métodos estáticos para trabajar con colecciones de tareas de forma sencilla. Uno de ellos es Task.WhenAll(), que nos permite esperar a que todas las tareas que le pasemos como argumento se completen, o Task.WhenAny() para esperar a que finalice sólo alguna de ellas.

Sin embargo, hasta ahora no existía una forma sencilla y directa de procesar las tareas conforme se fueran completando. O bien esperábamos a que todas finalizaran con Task.WhenAll(), o bien teníamos que crear un bucle que fuera esperando la finalización de la siguiente tarea con Task.WhenAny(), saliendo cuando todas hubieran acabado.

Afortunadamente, .NET 9 ha añadido el nuevo método llamado Task.WhenEach() que lo pone más fácil, permitiéndonos detectar sobre la marcha la finalización de las tareas conforme va ocurriendo. Esto nos facilita el proceso de sus resultados secuencialmente, en el orden en el que se completan y sin esperar a que acaben todas.

Vamos a verlo, pero para entenderlo mejor, primero vamos a recordar las opciones que teníamos antes de la llegada de este método.

martes, 29 de octubre de 2024
Piezas de un puzzle sugiriendo que todo va encajando en .NET

Seguimos analizando las novedades de .NET 9, y en esta ocasión le toca el turno a la actualización en miembros de clases parciales de C# 13.

Las clases o tipos parciales de C# están con nosotros casi desde los inicios de .NET, desde los tiempos de C# 2.0. Como sabemos, éstas permiten dividir la implementación de un tipo en varios archivos. Su existencia se debía a varios motivos:

  • Permitir a los desarrolladores trabajar en clases de forma concurrente sin bloquearse (recordemos que por aquellos tiempos aún pululaban por ahí infames sistemas de control de código fuente como SourceSafe).
  • Trocear clases extensas para facilitar su comprensión y mantenimiento.
  • Y, el que es más importante, posibilitar el uso sencillo de herramientas de generación de código que, de alguna forma, tuvieran que completar la implementación de código del usuario.

Acompañando a las clases parciales, se introdujo también el soporte para métodos parciales. Aunque su uso era limitado y podía depararnos algunas sorpresas, eran útiles para comunicar de forma segura distintos fragmentos de la clase. Básicamente, consistía en que una porción de una clase podía definir un método e invocarlo desde su código, delegando a otras porciones su implementación. Si no se implementaba en ninguna otra parte, simplemente no se generaba el código de la invocación.

Veamos un ejemplo del uso de estas características:

// Archivo: Ejemplo.Main.cs
public partial class Ejemplo
{    
    // El método parcial se declara sin implementación...
    partial void Log(string msg);

    public void RealizarAlgo()
    {
        hacerAlgoComplejo();
        Log("¡Ojo!"); // Usamos el método
                    // parcial declarado antes
    }
}

// Archivo: Ejemplo.Log.cs
public partial class Ejemplo
{
    partial void Log(string msg)
    {
        Console.WriteLine(msg);
    }
}

Pues bien, casi dos décadas más tarde, los tipos parciales van a recibir algunas actualizaciones interesantes en C# 13, posibilitando que, además de métodos, puedan definirse propiedades parciales.

martes, 22 de octubre de 2024
Imagen decorativa sugiriendo sistemas de almacenamiento en la nube

Una de las novedades más destacables de .NET 9 es, sin duda, el nuevo sistema de caché híbrida (Hybrid cache), una puesta al día del sistema de caché distribuida que nos acompaña desde las primeras versiones de .NET Core, y al que iban haciendo falta ya algunas mejoras.

Este nuevo sistema está construido encima de la infraestructura de caching existente (Microsoft.Extensions.Caching), añadiendo mejoras que hacen que su uso sea más sencillo y contemplando de serie funcionalidades que antes nos veíamos obligados a implementar manualmente.

Vamos a echarle un vistazo a sus principales características :)

Disclaimer: lo que vamos a ver a continuación está basado en .NET 9 RC2, por lo que todavía es posible que haya cambios en la versión final.

martes, 15 de octubre de 2024
.NET 9 apareciendo en el horizonte

Cada vez que llega una nueva versión del framework, todos estamos ansiosos por conocer las novedades, especialmente las más espectaculares. Sin embargo, a veces hay pequeñas mejoras que, sin ser revolucionarias, nos pueden resultar útiles en algunos escenarios.

El modificador params permite especificar que un método o función acepta un número variable de argumentos, como en el siguiente ejemplo, donde vemos una función Sum() que puede ser invocada con cualquier número de valores:

// Declaración de la función:
int Sum(params int[] values) 
{
    return values.Sum();
}

// Ejemplos de uso:
Console.WriteLine(Sum(1)); // Enviamos un único valor -> "1"
Console.WriteLine(Sum(1, 2, 3, 4, 5)); // Enviamos cinco valores -> "15"
Console.WriteLine(Sum()); // No enviamos elementos, se recibirá un array vacío -> "0"
Console.WriteLine(Sum([1, 2, 3])); // Aquí pasamos un array directamente -> "6"

Hasta ahora, el parámetro param sólo podía ser un array, lo que limitaba un poco su uso. Por ejemplo, no podríamos llamar a la función Sum() pasándole directamente un List<int> o un IEnumerable<int> sin convertirlos antes a array porque fallarían en tiempo de compilación, al no tratarse de un int[], que es el tipo del parámetro esperado.

¿Qué ha cambiado?

martes, 8 de octubre de 2024
Imagen de una fábrica creando GUIDs

Probablemente, todos hemos usado en algún momento el método Guid.NewGuid() de .NET para generar identificadores únicos globales. Y muchos hemos sufrido con el problema que supone su (pseudo) aleatoriedad, principalmente cuando necesitamos ordenarlos... sobre todo si su destino final es acabar siendo el campo clave de una base de datos relacional.

Pero pocos éramos conscientes de que el método Guid.NewGuid() de .NET retorna un GUID versión 4, y que existen otras versiones de UUID (¡hasta 8!) que pueden resultar más beneficiosas en determinados escenarios.

martes, 24 de septiembre de 2024
.NET

Acercándose el lanzamiento de .NET 9 el próximo mes de noviembre, podemos ver ya algunas de las novedades que traerá la nueva versión, con las que podemos jugar desde hace algunos meses instalando las previews que van apareciendo cada mes.

En esta ocasión vamos a ver algunas novedades más interesantes que se han añadido a la biblioteca de serialización JSON oficial, conocida por todos como System.Text.Json:

¡Vamos a ello!

Nota: Este artículo está basado en .NET 9 RC1, por lo que algunas de las características descritas podrían cambiar en la versión final.

1. Opciones de personalización de la indentación

Hasta .NET 8, las opciones de personalización relativas al indentado de JSON generado era bastante limitada. De hecho, sólo podíamos decidir si lo queríamos indentar o no usando la propiedad WriteIndented de JsonOptions:

var obj = new
{
    Name = "John",
    Age = 30,
    Address = new { Street = "123 Main St", City = "New York" }
};

var jsonOptions = new JsonSerializerOptions()
{
    WriteIndented = true, // Queremos indentar el JSON
};

Console.WriteLine(JsonSerializer.Serialize(obj, jsonOptions));

El resultado será siempre un JSON indentado a dos espacios como el siguiente:

{
  "Name": "John",
  "Age": 30,
  "Address": {
    "Street": "123 Main St",
    "City": "New York"
  }
}

En .NET 9 disponemos de dos nuevas propiedades para personalizar la indentación del JSON generado. La propiedad IndentCharacter permite especificar el carácter a utilizar (espacio o tabulador, exclusivamente), y mediante IndentSize podemos indicar el número de caracteres a utilizar en cada nivel de indentación.

var jsonOptions = new JsonSerializerOptions()
{
    WriteIndented = true,
    IndentCharacter = '\t',
    IndentSize = 2
};

Console.WriteLine(JsonSerializer.Serialize(obj, jsonOptions));

En este caso, el resultado será así (2 tabuladores por nivel de indentación):

{
                "Name": "John",
                "Age": 30,
                "Address": {
                                "Street": "123 Main St",
                                "City": "New York"
                }
}

2. Singleton de opciones de serialización y deserialización para la web

ASP.NET Core utiliza una configuración por defecto para serializar y deserializar objetos JSON, que ahora está disponible de forma pública en la propiedad JsonSerializerOptions.Web.

El objeto retornado es un singleton JsonSerializerOptions de sólo lectura, que estará disponible en cualquier punto de la aplicación.

Por defecto, este objeto está configurado de la siguiente manera:

  • PropertyNameCaseInsensitive está establecido a true, por lo que la deserialización de propiedades es insensible a mayúsculas y minúsculas.
  • JsonPropertyNamingPolicy tiene el valor JsonNamingPolicy.CamelCase, así que las propiedades se serializan en formato camelCase, el habitual en JavaScript y JSON.
  • NumberHandling es JsonNumberHandling.AllowReadingFromString, así que será posible deserializar números que vengan especificados como texto.

Un ejemplo de utilización de este objeto sería el siguiente:

var obj = new
{
    Name = "John",
    Age = 30,
    Address = new { Street = "123 Main St", City = "New York" }
};

Console.WriteLine(JsonSerializer.Serialize(obj, JsonSerializerOptions.Web));

// Resultado:
// {"name":"John","age":30,"address":{"street":"123 Main St","city":"New York"}}

Dado que el objeto es de sólo lectura, no podemos modificar sus propiedades. Si necesitásemos algún tipo de cambio en la configuración, podríamos crear una configuración nueva partiendo de él, e introducir las modificaciones deseadas, por ejemplo:

var jsonOptions = new JsonSerializerOptions(JsonSerializerOptions.Web)
{
    WriteIndented = true,
    IndentCharacter = '\t',
    IndentSize = 2
};

3. Soporte de anotaciones de anulabilidad

En .NET 8 y versiones anteriores, System.Text.Json no respetaba las anotaciones de anulabilidad de C# en las propiedades de los tipos de referencia. Por ejemplo, si tenemos una propiedad de tipo referencia que no puede ser nula como en el siguiente caso, podemos ver que el siguiente código deserializará sobre ella un valor nulo sin problema:

var myFriend = JsonSerializer.Deserialize<Friend>("""{ "Name": null }""");
Console.WriteLine(myFriend.Name ?? "Unknown"); // Muestra "Unknown"

// Friend tiene un campo "Name" que no puede ser nulo
public record Friend (string Name);

A partir de .NET 9, podemos indicar opcionalmente a System.Text.Json que respete la anulabilidad de propiedades, es decir, si una propiedad no es anulable, al deserializarla no podremos establecerla al valor nulo.

Esto podemos conseguirlo simplemente estableciendo a true la propiedad RespectNullableAnnotations de las opciones de deserialización:

var opt = new JsonSerializerOptions() { RespectNullableAnnotations = true };
var myFriend = JsonSerializer.Deserialize<Friend>("""{ "Name": null }""", opt);
Console.WriteLine(myFriend.Name ?? "Unknown"); // Lanza una excepción

Según la documentación, otra posibilidad es hacerlo de forma global para todo el proyecto, añadiendo la siguiente configuración en el archivo .csproj:

<ItemGroup>
  <RuntimeHostConfigurationOption 
     Include="System.Text.Json.JsonSerializerOptions.RespectNullableAnnotations" 
     Value="true" />
</ItemGroup>

Supongo que, debido a que aún estamos jugando con versiones preview del compilador, esta última opción no conseguí que me funcionara.

Otra cosa que he visto curiosa es que el siguiente código, a mi entender, debería lanzar una excepción, pero no lo hace:

var opt = new JsonSerializerOptions() { RespectNullableAnnotations = true };

var myFriend = JsonSerializer.Deserialize<Friend>("{ }", opt);
Console.WriteLine(myFriend.Name ?? "Unknown"); // Muestra "Unknown"

En principio, si la propiedad Name hemos dicho que no era anulable, ¿no debería lanzar una excepción al deserializar un objeto que no especifica su valor? Pues no, y parece ser que se trata de un comportamiento esperado; hay un interesante hilo al respecto en GitHub discutiendo el por qué de esta decisión, aunque en el siguiente apartado veremos que existe en algunos casos existe una forma alternativa de evitar esta situación.

Por último, la propiedad RespectNullableAnnotations también afecta a la serialización, de forma que si una propiedad no anulable tiene un valor nulo al ser serializada, se lanzará una excepción:

var opt = new JsonSerializerOptions() { RespectNullableAnnotations = true };

var myFriend = new Friend(null);
Console.WriteLine(JsonSerializer.Serialize(myFriend, opt)); // Lanza una excepción

4. Comprobación de parámetros obligatorios del constructor

En .NET 8 y anteriores, los parámetros del constructor del tipo a deserializar eran tratados como siempre como opcionales. Por esta razón, el código siguiente no provoca errores:

var myFriend = JsonSerializer.Deserialize<Friend>("""{ }""");
Console.WriteLine(myFriend.Name ?? "Unknown"); // Muestra "Unknown"

public record Friend(string Name);

En .NET 9, es posible indicar al deserializador que queremos respetar los parámetros requeridos del constructor, de forma que si no pueden ser satisfechos se lanzará una excepción. Esto se consigue con el nuevo flag RespectRequiredConstructorParameters en las opciones de deserialización:

var opt = new JsonSerializerOptions() { RespectRequiredConstructorParameters = true };

var myFriend = JsonSerializer.Deserialize<Friend>("""{ }""", opt);
Console.WriteLine(myFriend.Name ?? "Unknown"); // Lanza una excepción

La excepción, de tipo JsonException es bastante clara en su texto de descripción: 'JSON deserialization for type 'Friend' was missing required properties including: 'Name'.'.

Como en el caso anterior, este comportamiento puede ser también configurado de forma global para todo el proyecto, añadiendo la siguiente configuración en el archivo .csproj:

<ItemGroup>
  <RuntimeHostConfigurationOption 
      Include="System.Text.Json.JsonSerializerOptions.RespectRequiredConstructorParameters" 
      Value="true" />
</ItemGroup>

Y como en el caso anterior, tampoco he conseguido que me funcione de esta forma 😆 Esperemos que las siguientes preview o la versión final lo solucionen.

5. Descripción de tipos .NET usando JSON Schema

La nueva clase estática JsonSchemaExporter expone el método GetJsonSchemaAsNode(), que permite obtener el esquema JSON de un tipo .NET en forma de objeto JsonNode.

Su uso es muy sencillo, simplemente pasamos las opciones a utilizar para serializar el esquema, y el tipo del que queremos obtenerlo. Observad el siguiente ejemplo:

var schema = JsonSchemaExporter.GetJsonSchemaAsNode(
    JsonSerializerOptions.Default, 
    typeof(Friend)
);
Console.WriteLine(schema);

public record Friend(string Name, string? nickName, int Age = 20);

El resultado que obtendremos es una descripción del tipo, siguiendo el estándar JSON Schema, que en este caso sería algo así:

{
  "type": [
    "object",
    "null"
  ],
  "properties": {
    "Name": {
      "type": "string"
    },
    "nickName": {
      "type": [
        "string",
        "null"
      ]
    },
    "Age": {
      "type": "integer",
      "default": 20
    }
  },
  "required": [
    "Name",
    "nickName"
  ]
}

Conclusión

En este artículo hemos visto algunas de las novedades que traerá la nueva versión de .NET 9 en la biblioteca de serialización JSON oficial, System.Text.Json. Aunque no son cambios revolucionarios, sí que son mejoras que facilitarán la vida a los desarrolladores que trabajamos con JSON en nuestras aplicaciones.

¡Espero que os haya resultado interesante!