Hangfire – utilizando com um container IoC

Facebooktwittergoogle_plusredditpinterestlinkedinmail

Após vermos como utilizar o Hangfire em um post anterior, e conforme prometido, vamos ver como utilizar um container IoC (inversão de controle) em conjunto com o Hangfire. Para começarmos, vamos ver como o Hangfire faz para criar um job com um objeto que nós desejamos. Por exemplo, imagine que temos a seguinte classe:

using System;
using System.Diagnostics;

namespace WebDemoHangfire
{
    public class DemoJob
    {
        public void Execute()
        {
            Debug.WriteLine($"Fire and forget: {DateTime.Now}");
        }
    }
}

Quando criamos um job, podemos criar da seguinte forma:

BackgroundJob.Enqueue(() => new DemoJob().Execute());

O que acontece nesse caso, é que nós somos responsáveis por criar uma instância de DemoJob, e passar a instância como parâmetro para o Hangfire, e esse cenário faz sentido em alguns casos, mas, em outros casos, e acredito que sejam a maioria, nós não precisaríamos nos preocupar em passar um objeto criado para o Hangfire, e sim apenas qual o método que queremos executar, para esses casos, nós podemos criar nosso job da seguinte forma:

BackgroundJob.Enqueue<DemoJob>(j => j.Execute());

Mais simples e prático certo? Mas, ainda temos um problema, pois dessa forma passamos ao Hangfire a responsabilidade em criar nossa classe, o que é bom, porém, por padrão, o Hangfire utiliza o Activator.CreateInstance para criar nossa classe, e só consegue criar classes que tenham um construtor sem parâmetros, o que resolve boa parte dos casos, mas, se nossa classe tivesse o código abaixo, nós teríamos um problema:

using System;

namespace WebDemoHangfire
{
    public class DemoJob
    {
        private ILogger logger;

        public DemoJob(ILogger logger)
        {
            this.logger = logger;
        }

        public void Execute()
        {
            logger.WriteLine($"Fire and forget: {DateTime.Now}");
        }
    }
}

O problema aqui é que precisamos receber um ILogger como parâmetro para que nosso Job funcione, e o Hangfire não sabe criar um objeto do tipo ILogger. Para resolver isso, usamos um container IoC, que já vimos alguns deles aqui anteriormente, assim como vimos como utilizá-los em uma aplicação console .net core e uma aplicação web asp.net core. Agora vamos à mão na massa, e para o exemplo de hoje, vamos nos basear na aplicação que fizemos em nosso post anterior sobre o Hangfire que está disponível no GitHub clicando aqui.

Após abrir a aplicação no Visual Studio 2017, vamos criar nossa interface ILogger com o conteúdo abaixo:

namespace WebDemoHangfire
{
    public interface ILogger
    {
        void WriteLine(string message);
    }
}

Agora vamos criar uma classe que implemente nossa interface, e vamos chamá-la de DebugLogger:

using System.Diagnostics;

namespace WebDemoHangfire
{
    public class DebugLogger : ILogger
    {
        public void WriteLine(string message) => Debug.WriteLine(message);
    }
}

E por fim, vamos criar a classe DemoJob que irá representar o nosso Job:

using System;

namespace WebDemoHangfire
{
    public class DemoJob
    {
        private ILogger logger;

        public DemoJob(ILogger logger)
        {
            this.logger = logger;
        }

        public void Execute() => logger.WriteLine($"DemoJob: {DateTime.Now}");
    }
}

Em nossa classe DemoJob estamos recebendo um objeto do tipo ILogger em nosso construtor, e quem será responsável por criá-lo e passar para nossa classe vai ser o container IoC. Agora vamos alterar a classe JobsController para que o método Get agende um job de nossa nova classe, para isso adicione a linha:

BackgroundJob.Enqueue<DemoJob>(j => j.Execute());

Agora, nós precisamos dizer ao Hangfire para usar o container e não mais o Activator, e para isso nós precisamos criar uma classe que herde de JobActivator, e sobrescrever o método ActivateJob. Vamos então criar uma classe com o nome ContainerJobActivator, com o código abaixo:

using Hangfire;
using System;

namespace WebDemoHangfire
{
    public class ContainerJobActivator : JobActivator
    {
        private IServiceProvider _serviceProvider;

        public ContainerJobActivator(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }

        public override object ActivateJob(Type type) => _serviceProvider.GetService(type);
    }
}

O que fizemos aqui, foi criar uma classe que recebe um IServiceProvider como parâmetro no construtor, que é o container responsável pela criação dos objetos que precisamos. E no método ActivateJob, nós pedimos ao container que crie para nós um objeto do tipo solicitado.

E por fim, nós precisamos alterar o método Configure da classe Startup, e a primeira modificação que devemos fazer nele é acrescentar em sua lista de parâmetros o IServiceProvider, deixando a sua assinatura dessa forma:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider serviceProvider)

Agora, antes da linha que faz a chamada à UseHangfireServer, nós devemos adicionar a seguinte linha:

GlobalConfiguration.Configuration.UseActivator(new ContainerJobActivator(serviceProvider));

O método completo fica dessa forma:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider serviceProvider)
{
	loggerFactory.AddConsole(Configuration.GetSection("Logging"));
	loggerFactory.AddDebug();

	GlobalConfiguration.Configuration.UseActivator(new ContainerJobActivator(serviceProvider));

	app.UseHangfireServer();
	app.UseHangfireDashboard();

	app.UseMvc();
}

Pronto, o Hangfire já sabe utilizar o container para criar nossas classes, mas, ainda falta nós informarmos ao container como ele cria as nossas classes, então vamos registrá-las, e para isso, vamos alterar o método ConfigureServices também da classe Startup, adicionando as seguintes linhas:

services.AddTransient<DemoJob>();
services.AddTransient<ILogger, DebugLogger>();

O método completo ficará da seguinte forma:

public void ConfigureServices(IServiceCollection services)
{
	// Add framework services.
	services.AddTransient<DemoJob>();
	services.AddTransient<ILogger, DebugLogger>();
	services.AddHangfire(x => x.UseSqlServerStorage("Data Source=localhost;Initial Catalog=hangfire;User id=hangfire;Password=hangfire;"));
	services.AddMvc();
}

Agora vamos rodar nossa aplicação e ver o resultado da execução impresso no console.

Nós podemos utilizar outro container IoC, como o Autofac por exemplo, e nós iremos ver como fazer isso em um próximo post, fique ligado em nosso espaço.

O código completo do nosso exemplo está no GitHub: https://github.com/rdakar/hangfire-com-container-ioc.

Facebooktwittergoogle_plusredditpinterestlinkedinmail

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *