Como 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 articulo vamos a ver como poder utilizar la inyección que trae .NET Core de serie en nuestros proyectos y con esto nos evitamos el deber tener otro aspecto de configuración adicional, con esto nuestros procesos de Background son mucho más sencillo 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, que su ejecución no paralice el funcionamiento normal de nuestro desarrollo. Por ejemplos, 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 cuando 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 se dispone de una interfaz Web en la que podemos ver todos los procesos que se tiene planificado y el resultado de las ejecuciones.

Si sé que muchos lo estareis pensando y es que para todos estos procesos en un desarrollo Cloud en un entorno Microsoft para ello hago uso de un servicio serverless como son las Azure Functions. En cierta, forma las Azure Functions son muchos más que WebJobs con lo cual no estamos comparando el mismo servicio. Además vayamos a un escenario en el que tenemos una Web en la que por las noches no tiene tráfico y el servidor digamos que esta prácticamente parado por lo que disponemos de una franja en la que podamos aprovecharla 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.

Como 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 se 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 es decir por ejemplo http://avenger.com/hangfire Donde podemos ver todos los procesos que están en ejecución y los resultados obtenidos.

Como podeis ver lo que hace Hangfire es montar otra aplicación independiente dentro de nuestro servidor Web aprovechando la misma infraestructura. Esto que provoca que sino 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 logica 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ás no me interese inyectarle las dependencias de mi proyecto a los procesos de HangFire porque 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 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 dependecias que tiene y las resuelve. El escenario no es el mismo y la ejecución tampoco. Por lo tanto, para mi 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 de 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 solution nuget

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 lo que hemos utilizado es establecer la configuración tal y como indica la página oficial de HangFire y en el ultimo 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: dashboard hangfire

Por poner un caso de uso implementaremos un servicio de envío de mail diario. Para ellos 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). Tambien 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 ya 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 linea 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. dashboard hangfire

Resumiendo

DotNet Core nos proporciona un sistema muy modular y eso hace que aplicaciones de 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. Si lo tengo que reconocer por si no lo habéis notado aun Hangfire es algo que me gusta mucho :).

Podéis ver el ejemplo en mi repositorio de Github

Happy codding