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 ;)

17 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 .net. Mostrar todas las entradas
Mostrando entradas con la etiqueta .net. Mostrar todas las entradas
martes, 20 de febrero de 2024
.NET

Hace poco, andaba enfrascado en el proceso de modernización de una aplicación antigua que, entre otras cosas, guardaba datos en formato JSON en un repositorio de archivos. Dado que se trataba de una aplicación creada con .NET "clásico", la serialización y deserialización de estos datos se realizaba utilizando la popular biblioteca Newtonsoft.Json.

Al pasar a versiones modernas de .NET, esta biblioteca ya no es la mejor opción, pues ya el propio framework nos facilita las herramientas necesarias para realizar estas tareas de forma más eficiente mediante los componentes del espacio de nombres System.Text.Json. Y aquí es donde empiezan a explotar las cosas 😉.

Si habéis trabajado con este tipo de asuntos, probablemente habréis notado que, por defecto, los componentes de deserialización creados por James Newton-King son bastante permisivos y dejan pasar cosas que System.Text.Json no permite. Por ejemplo, si tenemos una clase .NET con una propiedad de tipo string y queremos deserializar un valor JSON numérico sobre ella, Newtonsoft.Json lo hará sin problemas, pero System.Text.Json nos lanzará una excepción. Esa laxitud de Newtonsoft.Json es algo que en ocasiones nos puede venir bien, pero en otras puede puede hacer pasar por alto errores en nuestros datos que luego, al ser procesados por componentes de deserialización distintos, podrían ocasionar problemas.

Por ejemplo, observad el siguiente código:

var json = """
           {
              "Count": "1234"
           }
           """;

// Deserializamos usando Newtonsoft.Json:
var nsj = Newtonsoft.Json.JsonConvert.DeserializeObject<Data>(json);
Console.WriteLine("Newtonsoft: " + nsj.Count);

// Intentamos deserializar usando System.Text.Json
// y se lanzará una excepción:
var stj = System.Text.Json.JsonSerializer.Deserialize<Data>(json);
Console.WriteLine("System.Text.Json: " + stj.Count);
Console.Read();

// La clase de datos utilizada
record Data(int Count);

Para casos como este, nos vendrá bien conocer qué son los custom converters y cómo podemos utilizarlos.

martes, 17 de octubre de 2023
.NET

Sabemos que está feo y no sea especialmente recomendable en muchos casos, pero hay veces en las que es totalmente necesario acceder a miembros privados de una clase. Por ejemplo, sería un requisito  imprescindible si fuéramos a implementar un serializador o deserializador personalizado, o necesitásemos clonar objetos, o simplemente realizar pruebas unitarias a una clase que no ha sido diseñada para ello.

Para conseguirlo, siempre hemos utilizado los mecanismos de introspección de .NET, conocidos por todos como reflexión o por su término en inglés reflection. Por ejemplo, imaginemos una clase como la siguiente, virtualmente inexpugnable:

public class BlackBox
{
    private string _message = "This is a private message";
    private void ShowMessage(string msg) => Console.WriteLine(msg);
    public override string ToString() => _message;
}

Aunque a priori no podemos hacer nada con ella desde fuera, usando reflexión podríamos acceder sin problema a sus miembros privados, ya sean propiedades, campos, métodos o cualquier tipo de elemento, como podemos ver en el siguiente ejemplo:

var instance = new BlackBox();

// Obtenemos el valor del campo privado _message:
var field = typeof(BlackBox)
    .GetField("_message", BindingFlags.NonPublic | BindingFlags.Instance);
var value = field!.GetValue(instance);

// Ahora llamamos al método privado ShowMessage():
var method = typeof(BlackBox)
    .GetMethod("ShowMessage", BindingFlags.NonPublic | BindingFlags.Instance);
method.Invoke(instance, [value]);

// Al ejecutar, se nuestra en consola: "This is a private message"

Sin embargo, de todos es sabido que la reflexión es un mecanismo muy lento, a veces farragoso en su implementación y, sobre todo, incompatible con tree-shaking, que es como se suele llamar la técnica que usan los compiladores de eliminar del código final todas las clases, métodos y propiedades que no se utilizan en el código final. Esta técnica va tomando cada vez más relevancia conforme los compiladores son capaces de crear directamente ejecutables nativos puros, porque permiten reducir de forma considerable el peso de los artefactos creados.

Por esta razón, en .NET 8 se ha incluido una nueva fórmula para acceder a miembros privados, mucho más eficiente y amistosa con AOT, porque se lleva a tiempo de compilación algo que antes obligatoriamente se resolvía en tiempo de ejecución.

martes, 26 de septiembre de 2023
.NET

Seguro que más de una vez habéis tenido que construir una abstracción sobre DateTime para poder controlar apropiadamente la obtención de la fecha/hora actual y otras operaciones relacionadas con el tiempo.

Suelen ser bastante útiles cuando creamos pruebas unitarias de métodos que dependan del momento actual. Por ejemplo, ¿cómo testearíamos de forma automática que las dos líneas de ejecución del siguiente método DoSomething() funcionan correctamente? Sería imposible salvo que ejecutásemos las pruebas a una hora determinada, algo que se antoja complicado 😉

public class MyClass
{
    public string DoSomething()
    {
        var now = DateTime.Now;
        return now.Second == 0
            ? "A new minute is starting"
            : "Current second " + now.Second;
    }
}  

Sin duda, una forma mejor y más test friendly sería contar con una abstracción sobre el proveedor de tiempos capaz de retornar la fecha y hora actual, por ejemplo:

public interface IDateTimeProvider
{
    DateTime GetCurrentDateTime();
}

De esta forma, podríamos reescribir la clase MyClass de forma que recibiera por inyección de dependencias nuestro proveedor IDateTimeProvider. Así sería realmente sencillo crear un par de pruebas unitarias que, suministrando los valores correctos a través de esta dependencia, podrían recrear los escenarios a cubrir:

public class MyClass
{
    private readonly IDateTimeServices _dateTimeServices;
    public TimeHelpers(IDateTimeServices dateTimeServices)
    {
        _dateTimeServices = dateTimeServices;
    }
    public string DoSomething()
    {
        var now = _dateTimeServices.GetCurrentDateTime();
        return now.Second == 0
            ? "A new minute is starting"
            : "Current second " + now.Second;
    }
}  

Aunque hacerlo de esta manera en nuestras aplicaciones es lo ideal, hay partes que se quedarían fuera de esta posibilidad, como las bibliotecas de terceros que de alguna forma dependan de las funcionalidades proporcionadas por DateTime.

Por esta razón, .NET 8 va a introducir una abstracción que nos permitirá gestionar estos escenarios de forma más homogénea y generalizada en aplicaciones y componentes .NET. 

Os presento la clase abstracta TimeProvider 😁

martes, 4 de julio de 2023
.NET

Seguro que habéis visto más de una vez un código parecido al siguiente, en el que llamamos a una API  REST externa y su resultado es deserializado a un objeto .NET para introducirlo en el flujo de la aplicación:

async Task<User[]> GetUsersAsync()
{
    var httpClient = _httpClientFactory.CreateClient();
    
    // Hacemos la llamada
    var response = await httpClient.GetAsync("https://jsonplaceholder.typicode.com/users");

    // Si la cosa no fue bien, retornamos
    if (!response.IsSuccessStatusCode)
        return Array.Empty<User>();

    // Descargamos la respuesta y la deserializamos
    var usersAsJson = await response.Content.ReadAsStringAsync();
    var users = JsonSerializer.Deserialize<User[]>(usersAsJson);

    return users;
}

Fijaos que el JSON de la respuesta de la API lo guardamos en una cadena de caracteres para, justo después, deserializarlo y convertirlo en un array de objetos User. Que levante la mano el que no lo haya hecho nunca 😉

¿Y veis dónde está el problema? A la salida de este método, tendremos en memoria dos copias de los datos de los usuarios, una en forma de string JSON y otra en el objeto que hemos deserializado.

Si estamos hablando de respuestas pequeñas o con poca concurrencia, probablemente el impacto es inapreciable. Pero si las estructuras retornadas por la API tuvieran un tamaño considerable o estamos en un escenario de múltiples llamadas simultáneas, esta duplicidad sería un auténtico derroche de recursos.

martes, 6 de junio de 2023
.NET

Hace unos días hablábamos de la serialización polimórfica en .NET 6, y vimos qué posibilidades teníamos para conseguirlo sin tener que escribir un custom converter o conversor personalizado. Y aunque realmente .NET 6 permite hacerlo, no es lo más elegante del mundo porque teníamos que operar sobre tipos object.

Pero por suerte, en .NET 7 la cosa ha mejorado y ya tenemos opciones razonables para conseguirlo basadas en los dos nuevos atributos [JsonDerivedType] y [JsonPolymorphic]. Veamos cómo utilizarlos.

martes, 30 de mayo de 2023
.NET

Imaginad una clase como la siguiente, que representa las características básicas de los archivos almacenados en una aplicación:

public class File
{
    public string FileName { get; set; }
    public ulong SizeBytes { get; set; }
}

Y ahora, imaginemos también una clase que hereda de la anterior para modelar específicamente, aunque también de forma resumida, los archivos de vídeo:

public class VideoFile: File
{
    public string Codec { get; set; }
    public TimeSpan Duration { get; set; }
}

Y puestos a imaginar, acabemos con el siguiente método, que retorna la representación JSON del objeto File que recibe como parámetro:

string SerializeFile(File file) => JsonSerializer.Serialize(file);

Gracias al polimorfismo, ese pilar imprescindible de la Programación Orientada a Objetos, podríamos invocar este método con objetos de tipo File, VideoFile o cualquier descendiente de alguno de ambos, puesto que en todos los casos se trata de objetos de tipo File:

var file = new File 
{ 
    FileName = "file.txt", SizeBytes = 1024 
};
Console.WriteLine(SerializeFile(file));

var videoFile = new VideoFile 
{ 
    FileName = "video.mp4", 
    SizeBytes = 1024 * 1024, 
    Codec = "H264", 
    Duration = TimeSpan.FromMinutes(3)
};
Console.WriteLine(SerializeFile(videoFile));

martes, 28 de marzo de 2023
C#

A raíz del artículo publicado hace algunas semanas sobre las ventajas de usar diccionarios en lugar de listas, me llegaba vía comentarios un escenario en el que se utilizaba una clase List<T> para almacenar objetos a los que luego se accedía mediante clave. Lo diferencial del caso es que dichos objetos tenían varias claves únicas a través de las cuales podían ser localizados.

Por verlo por un ejemplo, el caso era más o menos como el que sigue:

public class FriendsCollection
{
    private List<Friend> _friends = new();
    ...
    public void Add(Friend friend)
    {
        _friends.Add(friend);
    }

    public Friend? GetById(int id) 
        => _friends.FirstOrDefault(f => f.Id == id);

    public Friend? GetByToken(string token) 
        => _friends.FirstOrDefault(f => f.Token == token);
}

Obviamente en este escenario no podemos sustituir alegremente la lista por un diccionario, porque necesitamos acceder a los elementos usando dos claves distintas. Pero, por supuesto, podemos conseguir también la ansiada búsqueda O(1) si le echamos muy poquito más de tiempo.

martes, 14 de marzo de 2023
C#

A veces, los problemas de rendimiento de las aplicaciones, o determinadas funcionalidades de ellas, vienen derivados del uso de estructuras incorrectas para almacenar los datos, ya sea en memoria, base de datos o en cualquier tipo de almacén.

En este post vamos a centrarnos en un caso específico que me he encontrado demasiadas veces en código real: el uso indebido del tipo List<T> cuando sólo nos interesa buscar en esta colección por una propiedad que actúa como identificador único del objeto T.

martes, 31 de enero de 2023
.NET

Seguimos hablando de problemas que es habitual solucionarlos de una determinada manera, quizás por costumbre, quizás por pereza, o tal vez por desconocimiento de que haya otras formas de hacerlo. En este caso, hablaremos de una necesidad que probablemente habréis tenido alguna vez: transformar un GUID a una cadena de caracteres eliminando los habituales guiones.

Es decir, dado un GUID con el valor 1f5772a6-91ca-4035-8b6d-9676ec6d0eaa, queremos obtener su representación como cadena de caracteres, pero eliminando los guiones, resultando "1f5772a691ca40358b6d9676ec6d0eaa".

martes, 8 de noviembre de 2022
C#

En código que veo, incluso escrito por mí un tiempo atrás, es muy habitual encontrar comparaciones de cadenas de caracteres en las que, para asegurar que el casing no sea tenido en cuenta, se fuerza una conversión de alguno de los operandos, o incluso ambos, a mayúsculas o minúsculas.

En escenarios muy simples esto funcionará bien y no tendrá contraindicaciones especialmente graves para nuestro sistema. Veamos unos ejemplos:

// Ejemplo 1: conversión de un único operando
int Calculate(string op, int a, int b)
{
    // Pasamos a minúsculas el operador para
    // asegurar que encaja con la constante
    if(op.ToLower()=="add")
    {
        return a+b;
    } 
    else if(op.ToLower()=="sub")
    {
        return a-b;
    }
    ...
}

// Ejemplo  2: conversión de ambos operandos
bool AreBrothers(User user1, User user2)
{
    // Pasamos a mayúsculas ambos apellidos por
    // si alguno se ha escrito usando otro casing
    var areBrothers = user1.Surname.ToUpper() == user2.Surname.ToUpper();
    return areBrothers;
}

Sin embargo, aunque pueda parecer despreciable, estas operaciones de transformación a mayúsculas o minúsculas tienen un coste importante, que se pone especialmente de manifiesto cuando estamos hablando de aplicaciones con mucha carga de usuarios, alojada en infraestructura muy ajustada o cuando se requiere un rendimiento extremo.

martes, 27 de septiembre de 2022
.NET

En un vídeo del canal de Nick Chapsas, al que por cierto os recomiendo suscribiros, he descubierto que .NET 7 introducirá un mecanismo para "decorar" parámetros, propiedades y miembros de tipo string de forma que podamos aportar información sobre el tipo de contenido que esperan almacenar.

Para que lo entendáis mejor, observad el siguiente ejemplo, una función que recibe un mensaje y un formato de fecha, y que escribe por consola la fecha actual en el formato indicado seguido del mensaje.

void Log(string message, string dateFormat)
{
    Console.WriteLine(DateTime.UtcNow.ToString(dateFormat) + " - " + message);
}

Log("Hello!", "dd/MM/yyyy hh:mm");

Desde el punto de vista del consumidor de la función Log(), gracias a las ayudas del IDE podremos deducir que el segundo parámetro de tipo string, llamado dateFormat, debería ser un formato de fecha válido en .NET. Sin embargo, el entorno de desarrollo no podrá ofrecer ningún tipo de ayuda a la hora de codificar la llamada ni detectar si se producen errores, pues no dispone de información suficiente sobre el tipo de contenido esperado en la cadena de texto que se le suministra.

martes, 21 de junio de 2022
.NET

De casualidad me he topado con un interesante cambio que .NET 5 introdujo en los componentes de serialización y deserialización System.Text.Json y que, al menos para mí, pasó totalmente desapercibido en su momento. Por eso, he pensado que quizás sea buena idea dejarlo por aquí, por si hay algún despistado más al que pueda resultar útil.

Como seguro sabéis, al usar los componentes de System.Text.Json para serializar o deserializar una clase, utilizamos el atributo [JsonIgnore] para marcar las propiedades que queremos que sean ignoradas al convertir desde y hacia JSON.

Por ejemplo, dada la siguiente clase:

class User
{
    public int Id { get; set; }
    public string Email { get; set; }
    [JsonIgnore]
    public string Token { get; set; }
}

En ella estamos indicando expresamente que la propiedad Token debe ser ignorada, por lo que ésta no aparecerá si serializamos un objeto a JSON:

var user = new User { Id = 42, Email = "john@server.com", Token = "ABCDEF"};
Console.WriteLine(JsonSerializer.Serialize(user));

// Result:
{"Id":42,"Email":"john@server.com"}

Y lo mismo ocurre en sentido contrario:

var jsonStr = "{ \"Id\": 42, \"Email\": \"john@server.com\", \"Token\": \"ABCDEF\"}";
var user = JsonSerializer.Deserialize<User>(jsonStr);
// ¡user.Token es nulo aquí!
martes, 31 de mayo de 2022
.NET

A veces, desde aplicaciones .NET de consola, escritorio, o incluso ASP.NET Core, puede resultar interesante conectarse con una hoja de Google Sheets para añadir filas de datos.

Hay varias formas de conseguirlo, pero aquí vamos a ver la que creo que es la más sencilla, pues permite evitar parte del engorroso workflow de OAuth y, lo que es mejor, podemos usarla sin necesitar credenciales de usuario desde, por ejemplo, un servidor o un proceso desasistido.

Ojo: las APIs de Google que vamos a ver son gratuitas, pero tienen limitaciones de uso que debéis conocer antes de utilizarlas.

A grandes rasgos, el proceso consta de los siguientes pasos, que seguiremos a lo largo del post:

  • Configuración del proyecto y credenciales en Google Developer Console.
  • Creación del documento Google Sheet en el que añadiremos las filas.
  • Consumo de las APIs de Google para añadir datos.

¡A por ello!

martes, 10 de mayo de 2022
.NET

Solemos pensar que el punto de entrada de una aplicación .NET, ya sea el método estático Main() del clásico Program.cs o el nuevo Program.cs de .NET 6 que utiliza top level statements, es lo primero que se ejecuta en nuestras aplicaciones, en realidad no es así. Hay vida antes de que nuestra aplicación empiece a correr ;)

Aunque obviamente su utilidad es limitada y no es algo que necesitaremos hacer a menudo (o probablemente nunca), es conveniente saber que existen varias formas de insertar código que se ejecute antes que lo que siempre hemos considerado el entry point de nuestra aplicación, y es lo que veremos en este post.

martes, 19 de abril de 2022
.NET

La semana pasada veíamos algunas alternativas para comprobar de forma eficiente si una cadena de texto contiene un JSON válido y, al hilo de dicho post, el amigo Javier Campos aportó vía Twitter una fórmula adicional mejor que las que vimos aquí.

El código en cuestión es el siguiente:

public bool IsJsonWithReader(string maybeJson)
{
    try
    {
        var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(maybeJson));
        reader.Read();
        reader.Skip();
        return true;
    }
    catch
    {
        return false;
    }
}

La estructura Utf8JsonReader ofrece una API de alto rendimiento para acceder en modo forward-only y read-only al JSON presente en una secuencia de bytes UTF8. Los métodos Read() y Skip() se encargan respectivamente de leer el primer token del JSON y saltarse todos sus hijos, con lo que en la práctica se recorrerá el documento completo, lanzándose una excepción en caso de ser inválido.

martes, 5 de abril de 2022
.NET

Al hilo del post Cómo recibir un JSON como string en una acción ASP.NET Core MVC, el amigo Alberto dejaba una interesante pregunta en los comentarios: ¿y si una vez hemos recibido el string, queremos validar que sea un JSON válido?

Obviamente, una forma sencilla sería intentar deserializarlo por completo a la clase de destino, siempre que ésta sea conocida. Para ello podríamos utilizar el método Deserialize<T>() del objeto JsonSerializer de System.Text.Json, disponible en todas las versiones modernas de .NET, de la siguiente manera:

martes, 15 de febrero de 2022
.NET

Seguimos descubriendo novedades aparecidas con .NET 6, y ahora le toca el turno a la nueva clase PeriodicTimer, una fórmula para la ejecución de tareas periódicas en contextos asíncronos que evita el uso de los clásicos callbacks a los que nos tenía acostumbrados el framework.

Como recordaréis, .NET dispone de un buen número de formas de implementar temporizadores, o timers, para ejecutar tareas en intervalos periódicos. El más conocido probablemente sea el clásico System.Threading.Timer, en el que especificábamos el callback o método que debía ejecutarse en cada intervalo de tiempo mediante un delegado (en el siguiente ejemplo, mediante una lambda):

var timer = new System.Threading.Timer(o =>
{
    Console.WriteLine("Hey! " + DateTime.Now.ToLongTimeString());
}, null, 0, 1000);

Console.ReadKey();
Hey! 12:25:51
Hey! 12:25:52
Hey! 12:25:53
Hey! 12:25:54
Hey! 12:25:55
_

Pero también existía System.Timers.Timer, que nos permitía lograr algo parecido, aunque esta el callback lo implementábamos mediante una suscripción al evento Elapsed del objeto:

var timer = new System.Timers.Timer(1000);
timer.Elapsed += (sender, eventArgs) =>
{
    Console.WriteLine("Hey! " + DateTime.Now.ToLongTimeString());
};
timer.Start();
Console.ReadKey();

Existían algunas fórmulas más específicas para plataformas concretas, como las clases System.Windows.Forms.Timer, System.Web.UI.Timer u otras. Sin embargo, todas coincidían en varias cosas:

  • Utilizaban callbacks de alguna u otra forma, lo que implica un cierto riesgo de leaks de memoria y problemas con los tiempos de vida de objetos cuando la cosa se complica.
  • Los callbacks no permitían código asíncrono, lo que podía llevarnos a luchar contra los engorrosos escenarios de ejecución de código asíncrono en entornos síncronos (async-over-sync).
  • Podían darse casos de superposición u overlapping entre las distintas ejecuciones, cuando éstas tardaban en completarse más que el intervalo de definido en el timer.
martes, 18 de enero de 2022
.NET

Versiones de .NET anteriores a la 6 no disponían de una fórmula específica para determinar si un tipo o interfaz está registrado como servicio en el sistema de inyección de dependencias.

La única forma de hacerlo era intentar resolverlo, usando métodos como GetService() o GetService<T>(), y comprobar si el resultado era null:

var myService = serviceProvider.GetService<IMyService>();
if(myService is null)
{
    // El servicio no está registrado, hacemos algo
}

¿Cuál es el inconveniente de esto? Si el servicio no está registrado, ninguno: la llamada retornará un nulo y listo. 

El problema viene cuando sí está registrado, pues estaremos forzando la resolución de un servicio que, en realidad, no necesitamos para nada, pues sólo nos interesaba saber si existía o no. Porque recordemos que la resolución de un servicio podría tener un coste importante en términos de rendimiento, memoria, o incluso efectos colaterales en el estado de la aplicación, especialmente si nuestro servicio depende de otros, que a su vez dependen de otros, etc.

martes, 23 de noviembre de 2021
.NET

Estamos muy acostumbrados a comenzar nuestros métodos realizando comprobaciones para evitar que pasen a nuestro código valores nulos que pudieran romper la aplicación.

Desde el principio de los tiempos, estas guardas han presentado el siguiente aspecto:

public class MyService
{
    public void MyMethod(object first, object second)
    {
        if(first == null) 
        {
            throw new ArgumentNullException("first");
        }
        if(second == null) 
        {
            throw new ArgumentNullException("second");
        }        
        // ...
    }
}    

¿Todo bien, verdad? El código es aparentemente correcto y fácil de comprender, pero... ¡demasiado extenso! Hemos consumido casi diez líneas de código sólo realizando comprobaciones de "fontanería", y ni siquiera hemos empezado a plantear la funcionalidad real del método.

Afortunadamente, con el tiempo C# ha ido evolucionando y mejorando sucesivamente este escenario tan frecuente.

martes, 13 de abril de 2021
.NET Core

A raíz de los posts sobre generadores de código (como éste y éste), un amigo del blog me escribió para ver si de alguna forma era posible examinar el código fuente generado para poder depurarlo con mayor facilidad.

Y en efecto, es posible. Pero en vez de responderle directamente, he pensado que sería mejor compartirlo por aquí, de forma que pueda resultar de utilidad para alguien más :)