Skip to the content

Umbraco 8 | Scheduled Tasks With Hangfire & Dependency Injection

I’ve used Hangfire to handle scheduled tasks since Umbraco 7 and it’s worked a treat. Since moving over to Umbraco 8 I had a few problems getting up and running.

I managed to get the basics of Hangfire up and running in a very short time thanks to the following link.

(Credit: Sebastiaan Janssen)

https://cultiv.nl/blog/using-hangfire-for-scheduled-tasks-in-umbraco/

This was very good blog article, and it made it so easy to initially get up and running with a basic Hangfire setup.

My problems started when I to wanted to use dependency injection with one of my Hangfire methods. This just didn’t work out of the box. I tried a couple of times to get this working following a few forum posts. I almost gave up and even coded a plan b which worked quite well.

I used the basic setup as per Sebastiaan’s post and simply called a method in an APIController. This did the trick. Unfortunately with this method I had to sacrifice some basic Hangfire logging and more importantly there was a risk of timing out when running long tasks.

This wasn’t a big sacrifice for me since my tasks were quite small and I could handle logging separately. Since moving to Umbraco 8 I’ve made a point to improve my code and do things the best way possible. That reason, and the fact it was annoying me that I couldn’t get it working gave me a kick to try one more time.

I got there in the end with the help from the following link

(Credit: Rasmus Fjord)

https://our.umbraco.com/forum/umbraco-8/96445-hangfire-dependency-injection

Again, this was a great article and it helped me to get dependency injection working with Hangfire. It’s quite a tricky process so there’s a few comments in there which made the post a little difficult to extract the information I needed. Therefore, I decided to write a blog post with everything in one place.

This blog article is basically a combination of both Sebastiaan & Rasmus’ work along with the problems I encountered along the way.

 

Umbraco Basic Setup

This article is assuming you have your Umbraco website already set up and working. This also assumes you are using Visual Studio. I recommend setting your visual studio solution up with two projects Umbraco.Core and Umbraco.Web. You can find more information regarding this here.

https://our.umbraco.com/documentation/Umbraco-Cloud/set-up/Working-With-Visual-Studio/

 

Installing Hangfire

Install NuGet packages on both projects

Install the two packages using Nuget package manager

Install-Package Hangfire

Install-Package Hangfire.Console

You will need to install these to both your core and web project.

Navigate through any reference issues

Once this is done re-build your Visual Studios solution and attempt to run the project. If you encounter the same problems as me, you should start getting some reference issues. For me this was complaining about Microsoft.Owin.

To fix these do the Visual Studio / Tools / Nuget Package Manager / Manage Nuget Packages for Solution.

For me I had to search the installed section for Microsoft.Owin. I could see that there were some inconsistencies. My Core project was on version 4.0.1 and my web was on version 3.0. These firstly need to be the same version. I updated my web project to 4.0.1. Don’t get tempted to update both projects to the latest version. I tried that and get more errors.

I then run the project and still got the same error even though both of my projects seemed to be on the same/correct version. After a while of fiddling around I noticed one more step back in ‘Manage Nuget Packages for Solution’ that needed doing. I noticed that there was a warning in the consolidate tab which needed taking care of. Once I did this the project build and run without a problem.

Create a custom dashboard within Umbraco to view your Hangfire tasks.

To make this step easy simply unzip this file and copy it into your web project within the App_Plugins Folder. Download Here

Create a new class in your core project

Create a new class in your project called UmbracoStandardOwinStartup.cs Copy and paste the following code into this file.

using Cultiv.Hangfire;
using Hangfire;
using Hangfire.Console;
using Hangfire.SqlServer;
using Microsoft.Owin;
using Owin;
using Umbraco.Web;

[assembly: OwinStartup("UmbracoStandardOwinStartup", typeof(UmbracoStandardOwinStartup))]
namespace Cultiv.Hangfire
{
public class UmbracoStandardOwinStartup : UmbracoDefaultOwinStartup
{
public override void Configuration(IAppBuilder app)
{
//ensure the default options are configured
base.Configuration(app);

// Configure hangfire
var options = new SqlServerStorageOptions { PrepareSchemaIfNecessary = true };
const string umbracoConnectionName = Umbraco.Core.Constants.System.UmbracoConnectionName;
var connectionString = System.Configuration
.ConfigurationManager
.ConnectionStrings[umbracoConnectionName]
.ConnectionString;

GlobalConfiguration.Configuration
.UseSqlServerStorage(connectionString, options);

// Give hangfire a URL and start the server
app.UseHangfireDashboard("/hangfire");
app.UseHangfireServer();
}
}
}

Update your web.config file app settings

In your web project open the web.config file and

search for

<add key="owin:appStartup" value="UmbracoDefaultOwinStartup" />.

You need to replace this line for

<add key="owin:appStartup" value="UmbracoStandardOwinStartup" />

Now in the web.config search for ‘Umbraco.Core.ReservedPaths’ and set this up the same as the following.

<add key="Umbraco.Core.ReservedPaths" value="~/hangfire" />

Re-build and check the settings tab within Umbraco backend

That’s it. You should be able to re-build your solution and run the project. If your project builds ok then go to the Umbraco backend and check out the settings tab. You will now notice a new custom dashboard within there for Hangfire. If you do then everything is working so far.

Now for the tricky stuff….

 

Getting Dependency Injection Working

This was quite tricky. I won’t get into the why? Instead I will try and simplify the steps required to get you up and running. If you are interested in what exactly is going on, then please refer back Rasmus’ solution.

https://our.umbraco.com/forum/umbraco-8/96445-hangfire-dependency-injection

Hack at the Hangfire.LightInject source code

Download the source code for Hangfire.LightInject here.

https://github.com/sbosell/Hangfire.LightInject

Open the solution in Visual Studio. Find the file called LightInjectJobActivator.cs and replace the whole file contents with the following code.

public class LightInjectJobActivator : JobActivator
{
    private readonly ServiceContainer _container;
    internal static readonly object LifetimeScopeTag = new object();

    public LightInjectJobActivator(ServiceContainer container, bool selfReferencing = false)
    {
        if (container == null)
            throw new ArgumentNullException("container");

        this._container = container;

    }

    public override object ActivateJob(Type jobType)
    {
        // IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
        // Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
        if (HttpContext.Current == null)
        {
            HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager", string.Empty),
                new HttpResponse(new StringWriter()));
        }

        // this will fail if you do self referencing job queues on a class with an interface:
        //  BackgroundJob.Enqueue(() => this.SendSms(message)); 
        var instance = _container.TryGetInstance(jobType);

        // since it fails we can try to get the first interface and request from container
        if (instance == null && jobType.GetInterfaces().Count() > 0)
            instance = _container.GetInstance(jobType.GetInterfaces().FirstOrDefault());

        return instance;

    }

    public override JobActivatorScope BeginScope()
    {
        // IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
        // Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
        if (HttpContext.Current == null)
        {
            HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager", string.Empty),
                new HttpResponse(new StringWriter()));
        }

        return new LightInjecterScope(_container);
    }

}

class LightInjecterScope : JobActivatorScope
{
    private readonly ServiceContainer _container;
    private readonly Scope _scope;

    public LightInjecterScope(ServiceContainer container)
    {  

        _container = container;

        _scope = _container.BeginScope();
    }

    public override object Resolve(Type jobType)
    { // IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
        // Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
        if (HttpContext.Current == null)
        {
            HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager", string.Empty),
                new HttpResponse(new StringWriter()));
        }

        var instance = _container.TryGetInstance(jobType);

        // since it fails we can try to get the first interface and request from container
        if (instance == null && jobType.GetInterfaces().Count() > 0)
            instance = _container.GetInstance(jobType.GetInterfaces().FirstOrDefault());

        return instance;

    }

    public override void DisposeScope()
    {
        // IMPORTANT: HACK to create fake http context for job to allow the LightInject PerWebRequestScopeManager to work correctly when running in background jobs
        // Umbraco is hardcoded to using MixedLightInjectScopeManagerProvider so its really really hard to get around so this hack is the easiest way to handle this.
        if (HttpContext.Current == null)
        {
            HttpContext.Current = new HttpContext(new HttpRequest("PerWebRequestScopeManager", "https://localhost/PerWebRequestScopeManager", string.Empty),
                new HttpResponse(new StringWriter()));
        }

        _scope?.Dispose();
    }
}

Now re-build the solution and grab the new Hangfire.LightInject.dll which will have been generated.

Include Hangfire.LightInject.dll within your Umbraco.Core Project

You will need to take this new DLL and reference this in just your core project. Please note I previously added it to the web project too. When I did this I noticed critical errors when cleaning the project in visual studio. It looks like the clean was overwriting some LightInject dlls causing some error with the following description Method 'Scan' in type 'AssemblyScanner' from assembly 'Umbraco.Core, Version=8.0.0.0. Once I removed the Hangfire.LightInject.dll from my web project I stopped getting these error and the clean solution worked just find within Visual Studios.

Create a few new .cs files.

So now you can pretty much just copy and paste the code Rasmus provided in his solution. You will need to create 3 files.

Create a new file: HangfireJobsComposer.cs and add the following code.

[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
    public class HangfireJobsComposer : IComposer
    {

        public void Compose(Composition composition)
        {

            // Configure hangfire
            var options = new SqlServerStorageOptions { PrepareSchemaIfNecessary = true };
            var connectionString = System.Configuration
                .ConfigurationManager
                .ConnectionStrings["umbracoDbDSN"]
                .ConnectionString;

            var container = composition.Concrete as LightInject.ServiceContainer;

            GlobalConfiguration.Configuration
                .UseSqlServerStorage(connectionString, options)
                .UseConsole()
                .UseLightInjectActivator(container);

composition.Register<InvitationJobs>(Lifetime.Singleton);

     }

Edit the UmbracoStandardOwinStartup.cs file to the following.

public class UmbracoStandardOwinStartup : UmbracoDefaultOwinStartup
        {

            public override void Configuration(IAppBuilder app)
            {
                //ensure the default options are configured
                base.Configuration(app);
                try
                {
                    var dashboardOptions = new DashboardOptions { Authorization = new[] { new UmbracoAuthorizationFilter() } };
                    app.UseHangfireDashboard("/hangfire", dashboardOptions);
                    app.UseHangfireServer();

  RecurringJob.AddOrUpdate<InvitationJobs>("DeleteOldInvitations", x => x.DeleteOldInvitations(null), Cron.HourInterval(23));

    }
 }
}

Create a new file: InvitationJobs.cs and add the following code.

public class InvitationJobs
{
private readonly IUmbracoContextFactory _umbracoContextFactory; public InvitationJobs(IUmbracoContextFactory umbracoContextFactory)
{
_umbracoContextFactory = umbracoContextFactory; } [AutomaticRetry(Attempts = 0)]
public void DeleteOldInvitations(PerformContext hangfire)
{
try
{

}
catch (Exception e)
{

}
}
}

Re-build and run the project

This should be it. Re-build your Umbraco projects and run. Go back to the Settings tab / Hangfire section and within a minute or so you should start to see the task processing.

About the author

David Armitage

Umbraco Community Builder | .Net MVC Developer | Umbraco Certified Master | Recruitment Professional

Hey Peeps,

I'm an entrepreneur and web developer with a passion for coding. I absolutely love working with Umbraco CMS and appreciate the Umbraco community even more.

I've got 10 years+ .Net experience and 7 years+ experience working in the recruitment industry, both coding and marketing job websites. I wanted to use my skills to help give something back to this awesome community and so I decided to build UmbraJobs.com.

My vision & fundamentals

I want this website to be a place where Umbraco Professionals and Umbraco Partners can advertise their services but at the same time I want to filter out all the under-qualified freelancers & agencies that take up the biggest part of other job & freelancer websites. We've all worked hard to gain our Umbraco certifications, partnership status, and help build the Umbraco community to what it is today. I want to see everyone get rewarded for their efforts.

Follow me on social media

If you're an Umbraco professional, partner, employer, recruiter or a passionate Umbraco community builder then I'm more than happy to hear from you. Follow me on my social media channels and please reach out if you have any needs, want help or consultation with anything Umbraco related or just want a general chat.

comments powered by Disqus

Blog Filter


How we can help?

Need help with an Umbraco project?

Need help with a project but not too sure who to approach? Send us your project brief and budget. We will provide a free consultation and can help you gather quotes from the best and most suitable freelancers or agencies.

Looking to hire an Umbraco Professional?

Have you got job vacancy and want to hire an Umbraco professional? Post a job on our website for free. Alternatively let us know your requirements and we will send suitable prospects your way.

Claim your free profile!

Are you an Umbraco Freelance Developer or Umbraco partner that wants to advertise on our site? If you work with Umbraco or registered as an Umbraco partner then you can create a profile for free.

Let's build the Umbraco Community

We're big on building the Umbraco community and we think you guys are awesome! If there's anyway at all we can help then please reach out.