NetCore Tutorials - HangFire + Dependency Injection


The purpose of this guide is to aid a developer setting up some dependency injection with HangFire so that you can create more complex method calls that might be housed inside services that require dependencies. I am using Unity for the sake of demonstration and familiarity. At the end, there will be some links to other dependency injection guides / sources.

We are going to start with what a service that has dependencies and houses the method call we want to fire-and-forget look like.


Tutorial Plan of Attack

  1. EmailService.cs - Create a new class that has some dependencies (EmailService).
  2. UnityConfig.cs - Create a UnityConfig to create, register, and return an IoC container.
  3. UnityJobActivator.cs & UnityScope.cs - Wire the Unity IoC container to a HangFire JobActivator object called UnityJobActivator.
  4. Code Snippet - Set the global JobActivator to our new UnityJobActivator.

NuGets


EmailService.cs

public class EmailService
{
    #region Dependencies

    private static IDataSource _dataSource { get; set; } = null;
    private static ILogger _logger { get; set; } = null;

    #endregion

    public EmailService()
    { }

    public EmailService([Dependency("TargetDatabase")]IDataSource dataSource, ILogger logger)
    {
        _dataSource = dataSource;
        _logger = logger;
    }

    public async Task SendEmailAsync(SendEmailDetails sendEmailDetails)
    {
        using (var message = new MailMessage(
            new MailAddress(sendEmailDetails.FromAddress),
            new MailAddress(sendEmailDetails.ToAddress)))
        {
            message.Body = sendEmailDetails.MessageBody;
            message.IsBodyHtml = true;
            message.Subject = sendEmailDetails.MessageSubject;

            using (var client = new SmtpClient { Host = sendEmailDetails.SmtpHost })
            {
                var errorMessage = string.Empty;

                try
                {
                    await client.SendMailAsync(message);
                    await _dataSource.UpdateEmailSentAsync(sendEmailDetails.Id);
                }
                catch (Exception ex)
                { await LogErrorAsync(sendEmailDetails, ex); }
            }
        }
    }
}

EmailService Breakdown

The EmailService does one thing, SendEmailAsync. In order to do that though, it needs to update a Database via a DataSource. There are going to be multiple objects of DataSource in our IoC container... that's not important in of itself, but helpfully demonstrates how to target a specific instance of "TargetDatabase" that corresponds to an IDataSource.

The second dependency is a more straight forward Logger of ILogger interface. We will be under the assertion for this example that dependencies have matching name interface paradigm.


Up Next

So now, for any call of the EmailService.SendEmailAsync we have to setup the dependencies of EmailService. This functionality is commonly stored in a UnityConfig.cs file.

UnityConfig.cs

public static class UnityConfig
{
    public static bool Initialized { get; set; } = false;

    private static Lazy _container =
        new Lazy(() =>
        {
            return new UnityContainer();
        });

    public static IUnityContainer InitializeUnityContainer(string targetDatabaseConnectionString)
    {
        if (!Initialized)
        {
            _container.Value.RegisterInstance(
                "TargetDatabase",
                new DataSource(targetDatabaseConnectionString));

            _container.Value.RegisterInstance(
                new Logger(loggingConnectionString));

            Initialized = true;
        }

        return _container.Value;
    }
}

UnityConfig Breakdown

This is a fairly common example of UnityConfig. There are many flavors of the same thing but it's generally a static class with a simple accessor to get the IoC container out configured the way you like - however you do it, thats the end goal. While it should only be called during startup, this isn't inherently threadsafe but we do have a small boolean check to prevent duplicate initialization calls. So we are finished here when we have wired up any dependencies that are needed by our Fire-and-Forget services. In our example, we definitely need an instance of IDataSource and ILogger.

Remember though that if you are distributed processing (i.e. one service generates work, one service processes work) you will need to make sure the work scheduler AND the work processor will have all the needed dependencies referenced to the projects. The work processor is the context of this guide, its dependencies need to be wired up in Unity or it will bomb out at runtime.

During Initialize, we are registering specific instances of objects to the IoC container. One is wired to the type DataSource and keyword string "TargetDatabase" whenever IDataSource is resolved, the other based on type Logger and will resolve to the instance of Logger for every ILogger resolution. This is an example of manual registration. It is not the only way of registering objects for the Unity IoC container.


Up Next

Now that we have a way to get the IoC container wired up and returnable, next we need to know how to integrate this IoC container with HangFire.

UnityJobActivator.cs and UnityScope.cs

public class UnityJobActivator : JobActivator
{
    private readonly IUnityContainer _container;

    public UnityJobActivator(IUnityContainer container)
    {
        _container = container ?? throw new ArgumentNullException(nameof(container));
    }

    public override object ActivateJob(Type jobType)
    {
        return _container.Resolve(jobType);
    }

    public override JobActivatorScope BeginScope(JobActivatorContext context)
    {
        return new UnityScope(_container.CreateChildContainer());
    }
}

public class UnityScope : JobActivatorScope
{
    private static IUnityContainer _container;

    public UnityScope(IUnityContainer container)
    {
        _container = container;
    }

    public override object Resolve(Type type)
    {
        return _container.Resolve(type);
    }

    public override void DisposeScope()
    {
        _container.Dispose();
    }
}

UnityJobActivator.cs & UnityScope.cs Breakdown

This is simply a necessary class that inherits from JobActivator (HangFire) that allows us to integrate Unity and HangFire. It will wire up the resolve type mechanisms in HangFire from our Unity IoC container as the source. UnityScope.cs is more of the same but for scopes. Nothing really to see here.

Up Next

With the Service written, Unity configured, and Unity wired up to the HangFire JobActivator, now we have to create the JobActivator on startup.

Setup

    // In Your OnStart / Startup / AppBuilder Section

    // Call this before you initialize a new BackgroundJobServer()
    JobActivator.Current = new UnityJobActivator(
        UnityConfig.InitializeUnityContainer(
            TargetDatabaseConnectionString));

    _backgroundJobServer = new BackgroundJobServer();

Setup Breakdown

This portion of the setup could be placed anwyere, so before you call new BackgroundJobServer(), you want to set the UnityJobActivator to the global JobActivator.Current instance. Here we demonstrate what that sample code might look like.

Summary

That leaves us with a pretty close to a fully functioning IoC container and HangFire wire-up example.

Extra Sources

HangFire.io - Passing Dependencies
HangFire.io - Using IoC Containers
HangFire.io - IoC Container NuGets