Utilidades
Debouncer, ExpirableValue, RateLimiter, TokenBucket, SlidingCounter, SerialQueue, colas en segundo plano, conversiones STR, INT y ToSQL, formateadores y extensiones.
El paquete Dinaup incluye un módulo de utilidades con primitivas de concurrencia y limitación, conversiones de tipos, formateadores y métodos de extensión. Todo está en el namespace Dinaup: no hay nada que instalar aparte.
using Dinaup;Primitivas de concurrencia y limitación
Debouncer
Retrasa una acción asíncrona y cancela las llamadas anteriores si llega otra dentro de la ventana. Para operaciones caras disparadas por eventos rápidos (teclas, desplazamiento, redibujados). Implementa IDisposable.
var debouncer = new Debouncer(async () => await GuardarBorradorAsync(), 500);
// Cada pulsación reinicia el contador; solo se guarda 500 ms tras la última
debouncer.Debounce();ExpirableValue y ExpirableValueAsync
Envuelven un valor con un tiempo de vida (expirationInSeconds). Al leerlo, si ha caducado, llaman a la función de refresco.
// Asíncrono: refresca desde una fábrica cada 60 s
var token = new ExpirableValueAsync<string>(async () => await PedirTokenAsync(), 60);
string actual = await token.GetValueAsync();
// Síncrono, con actualizador o con valor inicial
var config = new ExpirableValue<int>(() => LeerConfig(), 30);
int n = config.GetValue();
var cache = new ExpirableValue<string>("valor inicial", 30);
cache.SetValue("otro valor"); // reinicia la caducidadRateLimiter
Limita intentos por clave. TryAttempt(key) incrementa el contador de esa clave y devuelve false si supera el máximo. Se reinicia sola al vencer clearInterval; NextClear dice cuándo.
var limiter = new RateLimiter(maxAttempts: 5, clearInterval: TimeSpan.FromMinutes(1));
if (limiter.TryAttempt(userEmail) == false)
throw new Exception("Demasiados intentos, prueba en un minuto.");TokenBucket
Cubo de tokens clásico. Se rellena a refillPerSecond tokens por segundo. TryConsume(count = 1) devuelve true si hay tokens y false si no.
var bucket = new TokenBucket(maxTokens: 100, refillPerSecond: 10);
if (bucket.TryConsume())
await LlamarApiExternaAsync();SlidingCounter
Limitador por ventana deslizante. TryAdd() devuelve true mientras el número de eventos dentro de la ventana no supere el máximo; descarta sola los eventos antiguos.
var counter = new SlidingCounter(maxEvents: 1000, window: TimeSpan.FromSeconds(10));
if (counter.TryAdd() == false)
return; // pico de actividad: descarta o encolaSerialQueue
Cola FIFO segura entre hilos para ejecutar tareas de una en una. Cada Enqueue devuelve un Task que completa cuando el trabajo termina. Admite Action, Func<T>, Func<Task>, Func<Task<T>> y Action con CancellationToken.
var queue = new SerialQueue();
// Se ejecutan en orden, nunca a la vez
await queue.Enqueue(async () => await EscribirEnArchivoAsync(a));
var total = await queue.Enqueue(() => Calcular());IBackgroundTaskQueue y QueuedHostedService
Cola de trabajo asíncrono con descripción por tarea. Enqueue(task, description) añade trabajo; DequeueAsync(cancellationToken) espera hasta que hay una tarea. QueuedHostedService es el BackgroundService que las consume: regístralo junto a la cola.
builder.Services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
builder.Services.AddHostedService<QueuedHostedService>();
// En un servicio
queue.Enqueue(async ct => await ProcesarPedidoAsync(pedidoId, ct), "procesar-pedido");
int pendientes = queue.Count();
var resumen = queue.ResumenCola(); // descripción -> cuántas pendientesSuscribible
Suscribible<T> envuelve un valor y ejecuta una acción cada vez que cambia: new Suscribible<int>(0, s => Console.WriteLine(s.Value)).
Conversiones de tipos
Métodos de extensión para convertir entre tipos sin TryParse a mano ni excepciones inesperadas.
| Familia | Convierte a | Ejemplos de uso |
|---|---|---|
STR | string | precio.STR(), id.STR(), fecha.STR(). Formatea decimales y fechas en el formato que espera Dinaup y tolera nulos. estado.STR(LenguajeE.Spanish) traduce un enum con MultiLanguageDescriptionAttribute. |
INT | int | texto.INT(), texto.INT(0) (valor si falla), texto.INT(min, max, siFalla) (con límites). |
ToDate y familia | DateTime, DateOnly, TimeOnly | s.ToDate(), s.ToDateOnly(), variantes _Nullable y de zona horaria. |
ToSQL y familia | Literal SQL | nombre.ToSQL(), fecha.ToSQL_Date(), importe.ToSQL_Decimal(), para concatenar en sentencias. |
ToSHA1 | Hash hexadecimal | ToSHA1(bytes), ToSHA1(texto), ToSHA1(stream). |
int cantidad = Request["cantidad"].INT(0); // 0 si no es número
string fecha = DateTime.UtcNow.ToSQL_Date(); // literal para SQL
string hash = Dinaup.extensions.ToSHA1(bytes); // SHA-1 en hexadecimalToSQL produce literales para concatenar, no parámetros. Para valores que llegan del usuario, usa consultas parametrizadas.
Formateadores
| Módulo | Qué hace |
|---|---|
FormatHelpers.FormatDate(fecha) y FormatRelativeTime(fechaUtc) | Fecha corta y tiempo relativo (hace 3 min). |
FormatHelpers.FormatDurationFromSeconds(segundos), FormatMillisecondsReadable, FormatSeconds, FormatMinutesReadable | Duraciones cortas legibles. Las variantes Html y Html20 devuelven marcado. |
FormatHelpers.FormatHoursReadable, FormatDaysReadable, FormatMonthsReadable, FormatYearsReadable | Duraciones largas legibles. |
FormatHelpers.FormatSizeBytes(bytes), FormatSizeMegabytes, FormatSizeGigabytes, FormatGrams(gramos) | Tamaños de archivo y pesos con su unidad. |
FormatHelpers.FormatByRolePn(role, valor) | Formatea un valor según el rol del campo Dinaup (RoleFieldE): moneda, porcentaje, peso. |
FormatHelpers.TryParseMagicDateTime(texto, ref fecha) | Interpreta fechas escritas de varias formas. |
TimeZoneHelper.GetTimeZone(zoneId), ToUtcTime(fechaLocal, zoneId), ToLocalTime_PN(fechaUtc, zoneId), GetLocalDateTime(zoneId), GetLocalDateOnly(zoneId) | Conversión entre UTC y una zona horaria IANA o Windows. |
Utilities.Pricing.CalculatePrice(quantity, unitPrice, fixedDiscount, percentDiscount, hasTaxesIncluded, taxPercent) y ApplyDiscounts(price, fixedDiscount, percentDiscount) | Precio de línea con descuento fijo, descuento porcentual e impuestos incluidos o no. Devuelve un CalculatedPrice. |
Utilities.MentionParser.GetAllMentions(html) | Los GUIDs mencionados en un comentario HTML. |
Para formatear según la cultura y la zona horaria del usuario en una app web, usa CultureService. Ver SDK .NET.
Familias de extensiones
El módulo añade cientos de métodos de extensión. Estas son las familias que más se usan.
Vacío y nulos
IsEmpty(),IsNotEmpty(),HasContent(): sobrestring,List,Dictionary,IEnumerable,Guidy fechas anulables.IsNull(),IsNotNull(),NotEmpty(): sobreGuidyobject.OrEmpty(): devuelve una secuencia vacía si el origen esnull, para iterar sin comprobar.IfIsEmpty(valor): valor por defecto para números y fechas vacíos.
if (report.Rows.IsNotEmpty()) { /* hay filas */ }
foreach (var x in lista.OrEmpty()) { /* nunca NullReferenceException */ }Predicados de texto
IsEmail(),IsGUID(),IsNumeric(),IsInteger(),IsJson(),IsDateOnly().ContainsWord(),ContainsIgnoreCase(),EqualsIgnoreCase(),StartsWithIgnoreCase(),EndsWithIgnoreCase().MatchAllWords(),MatchPercentage(),ContainsWebSearchMode(): búsqueda por palabras al estilo buscador.
LikeM
Atajo para preguntar si un valor es igual a alguno de una lista. Hay sobrecargas para int, decimal, Guid, char y string.
if (estado.LikeM(1, 2, 5)) { /* 1, 2 o 5 */ }
if (letra.LikeMIgnoreCase("A", "B", "C")) { /* a, b o c */ }Guardas de argumentos
Lanzan una excepción descriptiva si el argumento no cumple.
ThrowIf_IsNull(),ThrowIf_IsEmpty(): sobrestring, colecciones,Guid, números y fechas.ThrowIf_IsNotGuid(),ThrowIf_IsNotEmail(),ThrowIf_IsNotUrl(),ThrowIf_IsZero(),ThrowIf_HasUpperCase().
email.ThrowIf_IsNotEmail(nameof(email));
sectionId.ThrowIf_IsEmpty(nameof(sectionId));Colecciones
GetM(clave)sobre diccionarios: devuelve el valor o el valor por defecto si la clave no existe, sin lanzar.GetM(clave, defecto)fija ese defecto.HasElements(),HasContent(): presencia sobre arrays, listas y diccionarios.GetRandom(),MoveItemUp(),MoveItemDown(),AddRange()sobreHashSet.
Transformación y numéricas
- Texto:
RemoveAccents(),RemoveSpecialCharacters(),UppercaseFirst(),ParseBetween(inicio, fin),Trim_PN()(Trimque tolera nulos),FormatJson(). - Numéricas:
Between(min, max),IsMultipleOf(),RoundMoney(decimales),Half(),HasDecimalPlaces().
Las extensiones de petición HTTP (GetUserIP, ReadGet) están en ASP.NET Core.