.NET

Cómo inyectar las dependencias de Dotnet Core en nuestros procesos de Hangfire

Hangfire es una librería en la que podemos ejecutar nuestros procesos en Background en nuestros desarrollos en .NET y .NET Core. En este artículo vamos a ver cómo utilizar la inyección que trae .NET Core de serie en nuestros proyectos y con esto,  evitarnos el tener otro aspecto de configuración adicional. Así, nuestros procesos de Background son mucho más sencillos de configurar y sobre todo, podemos testearlos al igual que cualquier otro elemento 😉

¿Qué es Hangfire?

En las aplicaciones Web hay ocasiones en las que necesitamos tener algunas tareas en Background cuya ejecución no paralice el funcionamiento normal de nuestro desarrollo. Por ejemplo, un proceso de envío de mails, un proceso de importación de datos, etc. Para esto, Hangfire es una librería que nos proporciona una serie de utilidades de serie como planificar cuándo queremos lanzar este proceso (día, hora, periodicidad), también proporciona un marco de reintentos (si algún proceso falla, se vuelve a lanzar), y por si fuera poco, también dispone de una interfaz Web en la que podemos ver todos los procesos que se tiene planificado y el resultado de las ejecuciones.

Sé que muchos lo estaréis pensando. Para todos estos procesos en un desarrollo Cloud en un entorno Microsoft hacéis uso de un servicio serverless como son las Azure Functions. Sí, en cierta manera, las Azure Functions son muchos más que WebJobs, con lo cual no estamos comparando el mismo servicio. Imaginemos un escenario en el que tenemos una Web sin tráfico nocturno y con el servidor prácticamente parado, por lo que disponemos de una franja que podemos aprovechar para todas estas tareas sin la necesidad de adquirir un servicio extra (con un coste asociado al mismo). Como muchas veces digo, para un determinado escenario, no hay solo una opción válida, sino que nos interesa conocer las alternativas y escoger la mejor (o la menos mala :P).

Para aquellos que es la primera vez que escuchan hablar sobre Hangfire les recomiendo que vayan a leer esté artículo que escribió el gran Juan Carlos Martínez y luego continuar con la lectura de este artículo.

¿Cómo funciona Hangfire dentro de una aplicación de .NET?

Cuando instalamos HangFire en nuestra solución y le indicamos proyecto web que vamos a utilizar HangFire, tenemos que hacer dos cosas:

  1. Elegir la unidad de almacenamiento donde va a residir toda la gestión de procesos. Esta unidad de almacenamiento puede ser desde un SQL Server, CosmosDB, MariaDb, Azure BlobStorage, etc
  2. El punto donde va a arrancar HangFire,  por ejemplo http://avenger.com/hangfire en el que podemos ver todos los procesos que están en ejecución y los resultados obtenidos.

Como podéis ver, lo que hace Hangfire es montar otra aplicación independiente dentro de nuestro servidor Web aprovechando la misma infraestructura. Esto provoca que, si no se indica nada, toda inyección de dependencia que tengamos en .NET Core no la tendremos en los procesos que implementemos en HangFire.

Cuando hablamos de inyección de dependencias, todos estamos pensando en las interfaces de nuestra lógica de negocio, etc… pero hay muchas más cosas que se inyectan en .NET Core que van desde las propias settings de configuración hasta el sistema de Logs. Por lo que, aunque pensemos que quizá no me interese inyectarle las dependencias de mi proyecto a los procesos de HangFire porque es un proceso rápido y eficaz y que solo se ejecuta una vez no requiere de inyección de dependencias, creo que estamos en un contexto diferente.

Necesitamos inyección de dependencias en un proceso en HangFire

Si en lugar de poner HangFire pusiera Azure Functions, tendríamos las dos opiniones:

  1. Gente partidaria de tener la inyección de dependencia
    • PROS: Puedo testear mis métodos
    • Contra: El arranque de un proceso cuya naturaleza sea breve, le estamos añadiendo un tiempo que en ocasiones es justo el tiempo que necesitamos
  2. Gente en contra
    • PRO: Simplicidad del proceso, velocidad de ejecución
    • Contra: Testeable solo en un entorno

Mi opinión en cuanto a Hangfire, es que el escenario en el que estamos no es el mismo que el que tenemos en una Functions, principalmente porque en nuestra aplicación arranca el servidor y hay carga todas las dependencias que tiene y las resuelve. El escenario no es el mismo y la ejecución tampoco. Por lo tanto, para mí, aunque HangFire es un subproceso dentro de nuestro servidor, en caso de que estos procesos requieran elementos del desarrollo (algo de lo más normal) debería tener una inyección de dependencia para tener un ciclo de vida “normal” dentro del desarrollo.

Manos a la obra

Partimos de la base que ya tenemos creado un proyecto de Dotnet

dotnet new webapi

Agregaremos los paquetes de Nuget necesarios para hacer uso de Hangfire

 dotnet add package hangfire.core
 dotnet add package hangfire.aspnetcore
 dotnet add package hangfire.memorystorage

Si todo ha ido correctamente y arrancamos la solución tendremos estos paquetes instalados

El siguiente paso sería, como en casi todo en Dotnet Core, añadir los middleware necesarios para que arranque Hangfire.

public void ConfigureServices(IServiceCollection services)
        {
            services.AddHangfire(configuration => configuration
                                   .SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
                                   .UseSimpleAssemblyNameTypeSerializer()
                                   .UseRecommendedSerializerSettings()
                                   .UseMemoryStorage());
            services.AddHangfireServer();
            services.AddControllers();
        }

En el Hangfire, hemos establecido la configuración tal y como indica la página oficial de HangFire y en el último puesto le hemos indicado que utilice como almacenamiento un almacenamiento en memoria. Para almacenar toda esta información existen multitudes de opciones como SQl Server, CosmosDB, Azure Blob Storage, etc.

En el método de configurar, añadiremos que arranque un Dashboard para que tengamos una visualización de los procesos que se están ejecutando.

   public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseAuthorization();
            app.UseHangfireDashboard();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }

Si arrancamos nuestra aplicación y nos dirigimos a la ruta “/hangfire” visualizamos la siguiente pantalla:

Por poner un caso de uso, implementaremos un servicio de envío de mail diario. Para ello, nos creamos una carpeta que se llame “Services”. En esta carpeta tenemos, por un lado, un servicio con su interfaz que envía mail (en este ejemplo lo simula, ya que no es parte de este artículo). También nos crearemos una interfaz para el servicio que vamos a implementar en Hangfire

La interfaz tendrá la siguiente estructura:

public interface ISendMailServices
{
    Task<bool> SendMailAsync(string mail, string message);
}

y la implementación seria la siguiente:

public class SendMailServices : ISendMailServices
{               
    public async  Task<bool> SendMailAsync(string mail, string message)
    {
        await Task.Delay(3000);
        return true;
    }
}

Ahora para el servicio que vamos a implementar en Hangfire tendremos la siguiente interfaz:

public interface IHangFireServices
{
    Task ScheduleJob();
}
    public class HangFireServices : IHangFireServices
    {
        private readonly IConfiguration configuration;
        private readonly ISendMailServices sendMailServices;

        public HangFireServices(IConfiguration configuration,ISendMailServices sendMailServices )
        {
            this.configuration = configuration;
            this.sendMailServices = sendMailServices;
        }
        public async Task ScheduleJob()
        {
            var email = this.configuration.GetSection("sendmail").Value;
            var message = "Hola q ase";
            await this.sendMailServices.SendMailAsync(email, message);            
        }
    }

Tuneando el arranque

En primer lugar, creamos la clase “HangfireActivator” que depende de una clase JobActivator que es la que configura la inyección de dependencias para el servicio de Hangfire.

public class HangfireActivator : JobActivator
{
    private readonly IServiceProvider serviceProvider;
    public HangfireActivator(IServiceProvider serviceProvider)
    {
        serviceProvider = serviceProvider;
    }
    public override object ActivateJob(Type jobType)
    {
        return serviceProvider.GetService(jobType);
    }
}

Una vez que tenemos el desarrollo por un lado, tendremos que inyectar las dependencias en nuestro StarUp, para ello dentro del método “ConfigureServices” añadiremos las siguientes líneas de código.

    services.AddTransient<ISendMailServices, SendMailServices>();
    services.AddTransient<IHangFireServices, HangFireServices>();

Ahora en el método “Configure”, en primer lugar añadiremos un nuevo parámetro IServiceProvider serviceProvider. Luego añadiremos la siguiente línea para resolver la inyección de dependencia dentro de Hangfire

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
{
    GlobalConfiguration.Configuration.UseActivator(new HangfireActivator(serviceProvider));

Para finalizar ya podemos crear el Job de Hangfire con una línea como la siguiente:

RecurringJob.AddOrUpdate<IHangFireServices>(hangfire => hangfire.ScheduleJob(), Cron.Weekly);

Si ahora ejecutamos el código y vamos a la pestaña “Tareas Recurrentes”, observamos que tendremos programada la ejecución de la tarea que acabamos de añadir.

Resumiendo

DotNet Core nos proporciona un sistema muy modular y eso hace que aplicaciones de un tercero se puedan integrar de una manera fácil y sencilla. En este artículo hemos visto cómo hacerlo con Hangfire. Como habéis podido observar, Hangfire es una librería muy potente y con multitud de opciones. Sí, lo tengo que reconocer. Por si no lo habéis notado aún, Hangfire es algo que me gusta mucho 🙂

Podéis ver el ejemplo en mi repositorio de Github

Happy codding!

PD: También puedes encontrar este artículo en mi blog personal.

Compartir
Publicado por
Adrián Díaz

Este sitio web utiliza cookies para que tengas la mejor experiencia de usuario. Si continuas navegando, estás dando tu consentimiento para aceptar las cookies y también nuestra política de cookies (esperemos que no te empaches con tanta cookie 😊)