Seguro que alguna vez os habéis encontrado con la necesidad de usar un servicio registrado como scoped desde el código de un servicio registrado como singleton. Un caso típico es cuando en una aplicación ASP.NET Core tenemos un servicio corriendo en segundo plano (vaya, un BackgroundService) y necesitamos acceder a servicios que usan bases de datos o cualquier otro recurso que está normalmente asociado al ámbito de una petición HTTP.
Si intentamos inyectar directamente un servicio scoped dentro de un servicio singleton, obtendremos un error al iniciar la aplicación, durante el proceso de validación del árbol de dependencias, ya que el contenedor de .NET no permite esta combinación:
System.AggregateException: 'Some services are not able to be constructed
(Error while validating the service descriptor 'ServiceType:
Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType:
MyBackgroundService': Cannot consume scoped service 'MyDbContext' from
singleton 'Microsoft.Extensions.Hosting.IHostedService'.)'
Una posibilidad (peligrosa: ¡no hacer!) es deshabilitar esta validación inicial, pero esto solo ocultaría el problema y casi con toda seguridad nos llevaría más tarde a errores en tiempo de ejecución que podrían ser difíciles de diagnosticar y, en el peor de los casos, incluso tumbarnos la aplicación.
En este post vamos a ver la forma correcta de resolver el problema creando ámbitos personalizados dentro del servicio singleton, y usándolos para resolver los servicios scoped que necesitemos.
Probablemente alguna vez habréis trabajado en Visual Studio con una solución .NET enorme, con decenas o centenares de proyectos. Es algo habitual en sistemas que tienen ya muchos años de vida, con una arquitectura muy compleja o que han ido creciendo con el tiempo sin un control adecuado.
En estos casos, el entorno de desarrollo tarda una eternidad en cargar la solución y quedar listo para su uso, el rendimiento de Visual Studio se resiente enormemente, con lo que cada acción que realicemos (compilar, ejecutar, depurar, etc.) se vuelve lenta y frustrante, y encima, al navegar por el código, todo se complicará bastante por la cantidad de proyectos y archivos que tendremos que manejar.
Y lo peor de todo es que es para nada. En la mayoría de los casos no es necesario tener todos los proyectos de la solución cargados para poder realizar nuestras tareas, porque lo habitual es que solo trabajemos con un subconjunto de ellos, normalmente bastante pequeño.
Afortunadamente, .NET permite crear filtros de solución (archivos .slnf), algo así como mini-soluciones que incluyen solo los proyectos que nos interesan en cada ocasión, mejorando el rendimiento de los entornos de desarrollo y facilitando la navegación por el código.
En este artículo vamos a ver qué son los filtros de solución, cómo crearlos y cómo usarlos.
En .NET, todos hemos usado string.Format() cientos de veces. Se trata de una de esas herramientas que llevamos desde siempre en nuestro cinturón y usamos sin pensar demasiado en las implicaciones de rendimiento que puede tener su uso intensivo en escenarios críticos.
Sin embargo, cuando lo utilizamos en aplicaciones de alto rendimiento, en bucles con muchas iteraciones o en sistemas con limitaciones de recursos, es importante pararse un poco a pensar sobre cómo optimizar su uso.
En este artículo exploraremos cómo mejorar el rendimiento de string.Format() utilizando la clase CompositeFormat en .NET, que nos permite evitar el coste de analizar la cadena de formato cada vez que se llama al método.
En .NET, es bastante habitual que nuestros métodos o funciones reciban objetos de tipo IEnumerable<T> con la intención de que iteremos sobre ellos para lograr el comportamiento deseado. Esta abstracción es muy poderosa, ya que nos permite trabajar con cualquier colección o secuencia de datos sin preocuparnos por su implementación concreta, pero, si no somos cuidadosos, puede llevarnos a errores y comportamientos inesperados.
Por ejemplo, es muy frecuente realizar múltiples enumeraciones sobre la misma instancia de IEnumerable<T>, algo que a veces puede ocurrir de forma inconsciente, y se convierte en un problema cuando la secuencia es muy extensa o su contenido no es replicable. De hecho, muchas herramientas y entornos de desarrollo nos advierten sobre este problema durante la edición y compilación para que estemos atentos.
Otro problema común es asumir que la secuencia es finita, algo que no tiene por qué ser siempre cierto. Las enumeraciones en este caso pueden llevar a bucles infinitos o a un consumo excesivo de recursos.
En este artículo exploraremos distintos escenarios que pueden darse y cómo podemos evitarlos.
No es algo que se utilice habitualmente, y a veces incluso es un detalle desconocido por los desarrolladores, pero las funciones lambda de C# pueden ser estáticas. Y no es porque sea algo nuevo, pues esta característica se introdujo en la versión 9.0 del lenguaje en 2020... simplemente son esos pequeños detalles que se van añadiendo al lenguaje sin hacer mucho ruido, y que a veces pasan desapercibidos.
De hecho, el uso de lambdas estáticas puede ayudarnos a escribir un código más seguro y predecible, además de tener cierto impacto en el rendimiento de nuestras aplicaciones, por lo que merece la pena conocerlas y saber cuándo y cómo usarlas.
En este post vamos a ver qué son las lambdas estáticas, cómo se implementan y cuándo y por qué deberíamos usarlas.
Normalmente, cuando registramos un servicio en el inyector de dependencias de .NET, lo hacemos una única vez. A veces registramos directamente el servicio, aunque lo habitual es que lo asociemos a una interfaz, de forma que en futuro podamos sustituir la implementación sin tener que cambiar el resto del código:
// Registramos el servicio asociado a una interfaz:
builder.Services.AddScoped<IInvoiceIssuer, InvoiceServices>();
Sin embargo, nada impide que el mismo servicio lo registremos varias veces asociándolo a distintas abstracciones, sobre todo si tenemos nuestras clases de servicio son muy extensas y estamos haciendo segregación de interfaces (la "I" de los principios SOLID):
// Registramos el servicio asociado a varias interfaces, de forma
// que en otros servicios sólo será necesario inyectar la interfaz
// concreta que necesitemos:
builder.Services.AddScoped<IInvoiceIssuer, InvoiceServices>();
builder.Services.AddScoped<IInvoiceSender, InvoiceServices>();
builder.Services.AddScoped<IInvoicePrinter, InvoiceServices>();
...
Pero fijaos que esto está introduciendo un sutil problema: cada vez que se inyecte una de estas interfaces, se creará una nueva instancia de InvoiceServices, lo cual, además de consumir más recursos, puede ser un problema si a su vez el servicio depende de otros componentes que no puedan o deban ser compartidos:
public class DoSomething
{
public DoSomething(IInvoiceIssuer issuer, IInvoiceSender sender, IInvoicePrinter printer)
{
// Aquí se habrán creado tres instancias de InvoiceServices
}
}
¿Cómo podemos solucionarlo?
La relación de muchos desarrolladores con las expresiones regulares es de amor-odio. Aunque indudablemente son una herramienta muy potente, su uso puede ser complicado y a menudo se convierten en un dolor de cabeza.
Pero hoy no vamos a hablar de su (oscura) sintaxis, ni de lo difícil que es depurarlas, ni de cómo utilizarlas en .NET, sino de distintas técnicas que pueden ayudarnos a disparar su velocidad de proceso, algo bastante importante si las utilizamos en los procesos críticos o hot paths de nuestra aplicación.
En este artículo vamos comparar el rendimiento de distintos escenarios de uso de expresiones regulares, y cómo podemos optimizar su uso en .NET.
Hace algún tiempo hablamos sobre el atributo [MemberNotNullWhen] y vimos cómo se podía usar para mejorar la seguridad frente a valores nulos en nuestras aplicaciones.
Como recordaréis, este atributo lo aplicábamos sobre miembros booleanos para asegurar que otro miembro de su misma clase no contendrá null, dependiendo del valor (true o false) del primero. El ejemplo que lo ilustraba era el siguiente:
public class Result<T>
{
[MemberNotNullWhen(true, nameof(Value))] // Si Success es true, Value no es null
[MemberNotNullWhen(false, nameof(Error))] // Si Success es false, Error no es null
public bool Success { get; init; }
public string? Error { get; init; }
public T? Value { get; init; }
// Constructores, factorías, etc
}
Pero también comentamos que existían muchos otros atributos que el compilador empleaba para afinar en la detección de posibles problemas similares en el código. Al usarlos, el compilador comprende mejor las intenciones de nuestro código respecto a la utilización de nulos, y puede generar advertencias que impiden posteriores errores en tiempo de ejecución, pero sólo cuando son estrictamente necesarias.
En este nuevo post, vamos a echar un vistazo a los siguientes:
[NotNullWhen][NotNullIfNotNull][MemberNotNull]
Estamos acostumbrados a usar el operador await de C# para esperar la finalización de tareas asíncronas representadas por objetos de tipo Task, Task<T>, ValueTask o ValueTask<T>, pero, ¿sabíais que en realidad podemos usarlo con cualquier tipo de objeto?.
En este artículo vamos a ver que es bastante sencillo, y lo ilustraremos con un ejemplo muy simple: cómo esperar un segundo usando la expresión await 1000 o await TimeSpan.FromSeconds(1) en lugar del clásico await Task.Delay(1000).
Si os gusta trastear con las previews de .NET en el mismo equipo en el que estáis desarrollando proyectos que usan versiones estables, es posible que al compilar encontréis en la consola o ventana Output de vuestro IDE favorito un mensaje de error parecido al siguiente:
NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
Básicamente, el sistema nos está informando de que estamos usando un SDK que aún está en fase de pruebas, o preview. Aunque esto no debería ser un problema porque el SDK debería ser totalmente compatible hacia atrás, simplemente es un recordatorio de que no es la versión estable y siempre podríamos encontrarnos algún problema.
Esto ocurre porque los comandos del SDK utilizan la última versión instalada en el equipo, por lo que, si hemos instalado una versión preliminar, será ésta la que se utilice. Podemos comprobarlo fácilmente ejecutando el siguiente comando en consola, que nos mostrará la versión del SDK que se está utilizando por defecto:
C:\> dotnet --version
10.0.100-preview.5.25277.14
Normalmente, el mensaje NETSDK1057 podemos ignorarlo sin problema, pero si por cualquier motivo queremos eliminarlo o simplemente queremos forzar el uso de una versión determinada del SDK de .NET en algún proyecto o de forma global, podremos hacerlo usando un archivo llamado global.json.
Desde la aparición de Blazor y sus distintos tipos de hosting de componentes (SSR, Server, WebAssembly), se intentó promover que estos fueran agnósticos respecto a su modo de ejecución. Sin embargo, en la práctica esto no es siempre posible, y en ocasiones necesitamos saber en qué modo se está ejecutando un componente para poder adaptarlo a las necesidades de la aplicación.
Hace más de un año, cuando aún Blazor 8 era la versión más reciente de este framework, vimos por aquí un truco para detectar si un componente Blazor estaba ejecutándose de forma estática (SSR) o interactiva. Y algunos años antes, ya habíamos visto también distintas formas para detectar si un componente interactivo estaba corriendo sobre Blazor Server o Blazor WebAssembly.
Aunque las soluciones propuestas funcionaban bien y solucionaban nuestro problema, eran simplemente trucos (o "hacks", algo retorcidos en algunos casos) para cubrir unas necesidades que no estaban bien resueltas desde el propio framework.
Con .NET 9 esto cambió, y desde esta versión de Blazor ya existen mecanismos nativos para detectar cuándo un componente se ejecuta en modo estático o interactivo, y en este último caso, qué modo de renderizado se está utilizando.
Vamos a ver cómo conseguirlo.
Cuando usamos tipos enumerados en C#, muchas veces buscamos seguridad. Los valores de un enum son constantes conocidas de antemano y se comprueban en tiempo de compilación, lo que evita asignaciones inválidas que podrían derivar en errores en tiempo de ejecución.
También, el hecho de poder acceder a los valores del enum utilizando nombres o identificadores descriptivos da lugar a un código más legible y fácil de mantener. Y encima, las ayudas para el autocompletado y descubrimiento de valores posibles que nos proporcionan los IDEs modernos nos ayudan a ser más productivos. Todo son ventajas 🙂
Sin embargo, los enum de C# sólo permiten que sus valores subyacentes sean numéricos ( byte, sbyte, short, ushort, int -el tipo por defecto-, uint, long o ulong). Esto puede suponer una limitación en casos en los que nos vendría bien disfrutar de todos los beneficios anteriores, pero usando valores string... es decir, deberíamos poder definir un tipo que pueda contener únicamente un conjunto de cadenas de texto predefinidas.
Algunos lenguajes como TypeScript, Python o Swift soportan de forma nativa la creación de enumerados de texto; en cambio, en C# no tenemos esta posibilidad. Pero bueno, no todo está perdido... gracias a la flexibilidad del lenguaje, podemos conseguir algo bastante parecido 😉

Hace unas semanas estuvimos hablando de los atributos englobados en la categoría de "caller information" de .NET: [CallerFilePath], [CallerLineNumber], [CallerMemberName] y [CallerArgumentExpression]. Como vimos, estos atributos permiten que un método o función conozca información sobre el método que lo ha llamado, como el nombre del archivo, el número de línea, el nombre del método o la expresión de argumento.
Sin embargo, ninguna de estas opciones nos permite saber cuál es la clase que contiene el código que ha llamado al método que está actualmente en ejecución, algo que podría venir bien en algunos escenarios. Por ejemplo, recientemente me lo he encontrado revisando un proyecto en el que se utilizaba el sistema de logging NLog, donde encontraba un código como el siguiente para obtener y almacenar de forma estática un logger para la clase actual:
public static class Program
{
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
public static void DoSomething()
{
try
{
Logger.Info("Hello world");
System.Console.ReadKey();
}
catch (Exception ex)
{
Logger.Error(ex, "Goodbye cruel world");
}
}
}
A la vista de estas líneas, puede parecernos difícil deducir qué hace ese método GetCurrentClassLogger() por detrás para saber cuál es la clase actual y crearle un logger específico para ella.
En este post, vamos a abrir el capó para ver la "magia" que hace esto posible.
En C#, existen atributos que no están diseñados para añadir metadatos consumibles en tiempo de ejecución, sino que están pensados para ser interpretados por el compilador.
Entre ellos, se encuentran los atributos englobados en la categoría "caller information", que ofrecen la posibilidad de que un método o función conozca información sobre el método que lo ha llamado, como el nombre del archivo, el número de línea, el nombre del método o la expresión de argumento.
Esto puede ser muy interesante en diversos escenarios, pero probablemente el ejemplo más claro lo encontramos en la generación de trazas y puntos de control, puesto que, en lugar de tener que escribir manualmente el nombre del método o el número de línea, podemos obtenerlos de forma automática.
Lo vemos a continuación.
En JavaScript es frecuente encontrar expresiones como esta para comprobar si un objeto tiene valor (o, al menos, si su valor es uno de los reconocidos como truthy):
const friend = getFriend(1); // Obtiene un objeto friend, o null si no existe
if (friend) {
// Hacer algo con el objeto friend
}
Debido a su sistema de tipos estricto, en C# no es posible hacer lo mismo de forma directa, tenemos que comprobar si el objeto es null, por ejemplo así:
var friend = GetFriend(1); // Obtiene un objeto friend, o null si no existe
if (friend is not null) {
// Hacer algo con el objeto friend
}
Sin embargo, con muy poco esfuerzo podemos hacer que C# acepte la sintaxis de JavaScript para ese tipo de chequeos de nulidad, implementando en nuestra clase un conversor implícito a bool. Lo vemos a continuación.
En nuestras aplicaciones, es relativamente frecuente tener que buscar una serie de valores dentro de un conjunto de datos. Existen muchos escenarios, como cuando tenemos que buscar una serie de palabras dentro de un texto largo, o comprobar si todos los caracteres de un string pertenecen a un conjunto de caracteres válidos.
Por ejemplo, centrándonos en este último caso, imaginad que queremos comprobar si el contenido de una cadena de texto de tamaño arbitrario contiene únicamente una serie de caracteres válidos (por ejemplo, las vocales). La implementación que se nos ocurriría de forma natural sería muy simple: recorrer la cadena de texto de entrada, y, por cada carácter, comprobar si está en el conjunto de caracteres permitidos. ¿Fácil, no?
Sin embargo, como veremos en este post, hay formas mucho más eficientes de hacerlo.
Publicado por José M. Aguilar a las 8:05 a. m.
Etiquetas: .net, .net8, .net9, optimización, rendimiento, trucos
Normalmente los servicios usados por nuestra aplicación ASP.NET Core los registraremos en el contenedor de dependencias, para luego ir satisfaciendo los requisitos de los distintos componentes gracias a los mecanismos de inyección de dependencias que nos proporciona el framework. De estos servicios, aquellos que se registran como scoped se liberarán automáticamente al finalizar el proceso de la petición.
Sin embargo, si instanciamos manualmente un servicio durante el proceso de la petición, tendremos que encargarnos nosotros mismos de liberarlo cuando ya no sea necesario. Los que tengan un ámbito de vida corto, normalmente los crearemos y destruiremos utilizando using en algunos de sus sabores (como bloque o con ámbito local implícito), pero cuando queremos que pueda ser compartido entre varios componentes, no es sería tan sencillo... si no fuera porque el framework ya lo ha tenido en cuenta 🙂
Para estos casos, ASP.NET Core proporciona el extensor RegisterForDispose(), que nos permite registrar un servicio para que se libere automáticamente al finalizar la petición.
Si lleváis algunos años en esto, seguro recordaréis que en ASP.NET "clásico", los settings de las aplicaciones eran valores inmutables. Se guardaban habitualmente en el célebre archivo Web.config, y cualquier cambio a este archivo implicaba reiniciar la aplicación para que los cambios tuvieran efecto.
En ASP.NET Core, los settings son mucho más flexibles: se pueden cargar desde diferentes orígenes, pueden almacenarse en distintos formatos, y es posible modificarlos en caliente sin necesidad de reiniciar la aplicación. Pero además, ofrecen otra capacidad que es interesante conocer: podemos detectar y reaccionar a cambios en los settings en tiempo real, es decir, en el mismo momento en que se producen.
Para ello, básicamente tendremos que bindear una clase personalizada a la sección de settings que nos interese, y luego utilizar la interfaz IOptionsMonitor, para registrar un manejador personalizado que se ejecute cada vez que se produzca un cambio en los valores de configuración que estamos monitorizando.
En este post vamos a ver cómo hacerlo, paso a paso.
Como sabemos, es muy sencillo implementar servicios en segundo plano alojados en el interior de aplicaciones ASP.NET Core porque el framework nos proporciona infraestructura que simplifica la tarea, permitiendo que nos enfoquemos en la lógica de negocio que queremos ejecutar en background en lugar de tener que preocuparnos de los aspectos de más bajo nivel necesarios para hacer que funcione.
En la práctica, basta con heredar de la clase BackgroundService y sobreescribir el método ExecuteAsync para implementar la lógica que queremos ejecutar en segundo plano. Por ejemplo, el siguiente servicio se encarga de escribir un mensaje en la consola cada segundo:
public class MyBackgroundService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
Console.WriteLine($"Current time: {DateTime.Now:T}");
await Task.Delay(1000, stoppingToken);
}
}
}
Luego tendríamos registrar este servicio en el contenedor de dependencias de ASP.NET Core, de forma que el framework lo detectará automáticamente y lo lanzará cuando la aplicación sea iniciada:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHostedService<MyBackgroundService>();
...
Al ejecutar, veremos que, en la consola donde hemos lanzado la aplicación, se escribirá periódicamente la hora desde la tarea en segundo plano:
Current time: 13:34:06
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:7080
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5148
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: D:\Projects\BackgroundServiceCrash
Current time: 13:34:07
Current time: 13:34:08
Pero bueno, no es esto de lo que quería hablar, sino de lo que ocurre cuando en uno de estos servicios en segundo plano se produce un error... porque ojo, que si no tenemos cuidado puede tumbar completamente nuestra aplicación.
Habitualmente los paquetes NuGet que usamos en un proyecto se registran en entradas <PackageReference> de su archivo .csproj. Es un mecanismo sencillo y efectivo cuando trabajamos con un único proyecto o un número reducido de ellos.
Sin embargo, cuando la cosa crece y estamos trabajando con muchos proyectos que usan paquetes comunes cuyas versiones deben estar alineadas, todo se complica un poco, y trabajar con ellos se puede convertir en algo bastante tedioso. Y más aún si no usamos una herramienta visual como NuGet Package Manager de Visual Studio.
Para estos escenarios, NuGet 6.2, lanzado en agosto de 2023 y presente por defecto en todas las instalaciones actualizadas de .NET y Visual Studio, introdujo el concepto de gestión centralizada de paquetes (en inglés, Central Package Management o CPM). Esta funcionalidad nos brinda la posibilidad de definir en un archivo independiente los paquetes utilizados, con sus versiones correspondientes, de forma que todos los proyectos de la solución puedan hacer referencia a él y mantener sus dependencias actualizadas.

