Saltar al contenido
Programemos.netDecisiones pragmáticas sobre .NET, Azure, arquitectura de software e IA.

Hangfire en ASP.NET Core: trabajos en segundo plano

Hangfire resuelve un problema concreto en ASP.NET Core: sacar de una petición HTTP el trabajo que no necesita terminar antes de responder. Enviar un correo, procesar un archivo o reintentar una integración son buenos ejemplos.

A diferencia de lanzar un Task.Run, Hangfire serializa el trabajo y lo guarda para que pueda sobrevivir a reinicios, registrar su estado y reintentarse. Esa persistencia es útil, pero no elimina las decisiones de operación: un trabajo puede ejecutarse más de una vez, fallar a mitad o quedarse esperando un recurso externo.

Qué es Hangfire y cómo funciona

Hangfire administra trabajos en segundo plano desde que se crean hasta que se ejecutan. Incluye persistencia, estados, reintentos y un Dashboard operativo. No sustituye una arquitectura de mensajería distribuida en todos los escenarios, pero resulta práctico cuando el trabajo pertenece a la misma aplicación .NET.

Su arquitectura tiene tres piezas:

  • Cliente: crea el trabajo y lo envía al almacenamiento.
  • Almacenamiento: conserva la definición, el estado y la información necesaria para procesarlo.
  • Servidor: obtiene trabajos del almacenamiento y los ejecuta. En una aplicación pequeña, el mismo proceso puede actuar como cliente y servidor.
Flujo entre el cliente, el almacenamiento y el servidor de Hangfire

Arquitectura básica de Hangfire

En el texto uso “trabajo”, que corresponde al término job de Hangfire. En artículos antiguos también lo llamaba “tarea”; aquí se refieren al mismo proceso en segundo plano.

Configurar Hangfire en ASP.NET Core

Para reproducir el ejemplo instala Hangfire.AspNetCore y Hangfire.MemoryStorage. En producción instala el proveedor correspondiente al almacenamiento elegido, como SQL Server o Redis.

En Program.cs, registra Hangfire y levanta un servidor:

Program.cs
builder.Services.AddHangfire(configuration => configuration.UseMemoryStorage());
builder.Services.AddHangfireServer();

El almacenamiento en memoria reduce el ruido del ejemplo, pero no es una opción de producción: al reiniciar la aplicación se pierde su estado. Para una aplicación real, usa almacenamiento persistente, mueve el trabajo a una clase inyectable y no serialices secretos ni información sensible en sus argumentos.

Tipos de trabajos en Hangfire

Trabajo inmediato o fire-and-forget

Un trabajo inmediato se crea sin esperar su resultado. Hangfire lo almacena y devuelve un identificador que permite seguir su estado.

app.MapPost("/notificacion", () =>
{
var jobId = BackgroundJob.Enqueue(() => SendEmail());
return Results.Accepted(value: new { jobId });
});

Enqueue no espera a que SendEmail termine. La API puede responder mientras un servidor de Hangfire procesa el trabajo.

Trabajos retrasados

Un trabajo retrasado establece un tiempo mínimo antes de que pueda comenzar:

app.MapPost("/espera", () =>
{
var jobId = BackgroundJob.Schedule(
() => SendEmail(),
TimeSpan.FromSeconds(10));
return Results.Accepted(value: new { jobId });
});

El término mínimo importa: la ejecución depende del sondeo, la disponibilidad del servidor y la carga. No uses un trabajo retrasado como si fuera un temporizador de tiempo real.

Trabajos recurrentes

RecurringJob.AddOrUpdate registra un trabajo con una frecuencia equivalente a cron:

app.MapPost("/recurrente", () =>
{
RecurringJob.AddOrUpdate(
"send-email",
() => SendEmail(),
Cron.Minutely);
return Results.NoContent();
});

El identificador send-email debe ser estable y único. Volver a llamar AddOrUpdate con ese identificador actualiza la definición existente en vez de crear otra.

Continuaciones

Una continuación crea un trabajo que depende de otro:

app.MapPost("/continuation", () =>
{
var jobId = BackgroundJob.Enqueue(() => SendEmail());
BackgroundJob.ContinueJobWith(jobId, () => CrearLog());
return Results.Accepted(value: new { jobId });
});

En un flujo real también debes decidir qué debe pasar con la continuación si el primer trabajo falla.

Proteger el Dashboard de Hangfire

En desarrollo puedes activar el Dashboard desde Program.cs:

app.UseHangfireDashboard();

La interfaz permite inspeccionar argumentos, reintentar, eliminar y disparar trabajos. No debe quedar expuesta públicamente. En ASP.NET Core, configura autenticación antes del Dashboard y exige una política:

Program.cs
app.UseAuthentication();
app.UseAuthorization();
app.MapHangfireDashboard("/hangfire")
.RequireAuthorization("HangfireDashboard");

La política debe limitar el acceso a operadores autorizados. Permitir a cualquier usuario autenticado sigue siendo demasiado amplio para muchas aplicaciones.

Dashboard de Hangfire con el estado de los trabajos

Dashboard de Hangfire

Llevar Hangfire a producción

Antes de desplegar revisaría, como mínimo:

  • almacenamiento persistente y copias de seguridad
  • idempotencia ante reintentos o ejecuciones duplicadas
  • timeouts, cancelación y política de reintentos
  • colas y cantidad de workers según el tipo de carga
  • autorización del Dashboard y ausencia de secretos en argumentos
  • logs, métricas, alertas y correlación con la petición original

Usaría Hangfire cuando una aplicación .NET necesita trabajos persistentes, reintentos y operación visible sin introducir todavía una plataforma de mensajería completa. No lo elegiría por defecto para integrar muchos servicios independientes, mover grandes volúmenes de eventos o resolver flujos que requieren garantías distribuidas más estrictas.

La decisión clave no es solo “ejecutar después”, sino definir qué ocurre cuando el trabajo falla o se repite. Hangfire aporta la infraestructura; esas decisiones siguen siendo responsabilidad del equipo.

Si quieres seguir aprendiendo sobre estos temas, te invito a ver mis otras publicaciones.

Referencias

Jose Antonio Arias
Jose Antonio Arias

Senior .NET Developer e Ingeniero en Informática con amplia experiencia en backend, Azure, DevOps, arquitectura e IA empresarial. Ayudo a convertir problemas y procesos de negocio en soluciones tecnológicas con valor real.