Resolving Hangfire dependencies/HttpContext in .NET Core Startup
Asked Answered
C

3

12

I've installed and configured Hangfire in my .NET Core web application's Startup class as follows (with a lot of the non-Hangfire code removed):

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.UseHangfireServer();
        //app.UseHangfireDashboard();
        //RecurringJob.AddOrUpdate(() => DailyJob(), Cron.Daily);
    }

    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.AddOptions();
        services.Configure<AppSettings>(Configuration);
        services.AddSingleton<IConfiguration>(Configuration);
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddScoped<IPrincipal>((sp) => sp.GetService<IHttpContextAccessor>().HttpContext.User);
        services.AddScoped<IScheduledTaskService, ScheduledTaskService>();

        services.AddHangfire(x => x.UseSqlServerStorage(connectionString));    
        this.ApplicationContainer = getWebAppContainer(services);
        return new AutofacServiceProvider(this.ApplicationContainer);
    }
}

public interface IScheduledTaskService
{
    void OverduePlasmidOrdersTask();
}

public class ScheduledTaskService : IScheduledTaskService
{
    public void DailyJob()
    {
        var container = getJobContainer();
        using (var scope = container.BeginLifetimeScope())
        {
            IScheduledTaskManager scheduledTaskManager = scope.Resolve<IScheduledTaskManager>();
            scheduledTaskManager.ProcessDailyJob();
        }
    }

    private IContainer getJobContainer()
    {
        var builder = new ContainerBuilder();
        builder.RegisterModule(new BusinessBindingsModule());
        builder.RegisterModule(new DataAccessBindingsModule());
        return builder.Build();
    }
}

As you can see, I'm using Autofac for DI. I've set things up to inject a new container each time the Hangfire job executes.

Currently, I have UseHangfireDashboard() as well as the call to add my recurring job commented out and I'm receiving the following error on the line referencing IPrincipal:

System.NullReferenceException: 'Object reference not set to an instance of an object.'

I understand that Hangfire does not have an HttpContext. I'm not really sure why it's even firing that line of code for the Hangfire thread. I'm ultimately going to need to resolve a service account for my IPrincipal dependency.

How can I address my issue with Hangfire and HttpContext?

Cutlip answered 18/5, 2017 at 17:41 Comment(13)
Did the answer provided solved your issue?Obsecrate
@KevinR. I've been wanting to try, but haven't had time. Once I'm able to try, I will mark your response as the answer if it worked.Cutlip
Why is Hangfire trying to resolve the .NET Core Startup class? Well how else is it going to call that instance method on Startup? It needs an instance. Think about how Hangfire would have to run this scheduled task if you started the server without Startup doing it, but you still have an expectation your previously registered task would run.Akiko
You can avoid it with something like AddOrUpdate<IScheduledTaskService>(x => x.DailyTask(), Cron.Daily) which will cause hangfire to resolve your service rather than StartupAkiko
@im1dermike IAppBuilder is not part of Asp.Net-Core. Core uses IApplicationBuilder was that a typo in the OP?Moyer
@im1dermike can you clarify the version of asp.net being used.Moyer
@Moyer Core 1.1. The main problem I'm having now is when I add UseHangfireServer, I then need to resolve HttpContext too.Cutlip
Can you show the IScheduledTaskManager and its dependency on HttpContext? You probably need to add the HttpContextAccessor to the DI container.Moyer
@Moyer I just updated my post to include the HttpContext-related code. It ends up throwing the error when I add UserHangfireServer, not even when I try to add a job.Cutlip
@im1dermike here is the thing. You are providing information piece by piece and the story is unfolding gradually. But you are not clearly explaining the problem as things change. Is the error the same one as before or a new error? The issue may be trivial but if it is not explained properly we can't provide much help and you end up frustrated trying to figure it out on your own.Moyer
@Moyer I completely agree. My apologies. I've unfortunately been sidetracked with higher priority items, but I just rewrote my question to more accurate describe my current situation.Cutlip
@im1dermike Ok trying to troubleshoot something. Where is the principal being used? HttpContext and its IPrincipal would not be available at startup so trying to narrow down the cause of the null reference error. I see where you add it to the service collection but not where it is being used.Moyer
@im1dermike I figured it out. You get the HttpContext issue because you have scoped dependencies which would require a HttpRequest. At startup there is no HttpContext and by extension requests. Try registering dependencies with another lifetime and that should take you past the UseHangfireServer.Moyer
M
5

The main problem I'm having now is when I add UseHangfireServer, I then need to resolve HttpContext too

Found here Using IoC containers

HttpContext is not available

Request information is not available during the instantiation of a target type. If you register your dependencies in a request scope (InstancePerHttpRequest in Autofac, InRequestScope in Ninject and so on), an exception will be thrown during the job activation process.

So, the entire dependency graph should be available. Either register additional services without using the request scope, or use separate instance of container if your IoC container does not support dependency registrations for multiple scopes.

resolving scoped dependencies in .net core would require a request which is not available during startup when registering and activating jobs. Therefore make sure that your service required for activation during startup are not registered using scoped lifetimes.

 services.AddTransient<IScheduledTaskManager, ScheduledTaskManageImplementation>();

All that is left now is to configure the application to use that service with the recurring job,

public class Startup {    
    public IContainer ApplicationContainer { get; private set; }

    public Startup(IHostingEnvironment env) {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();
        Configuration = builder.Build();
    }

    public void Configuration(IApplicationBuilder app) {
        // app.AddLogger...

        //add hangfire features
        app.UseHangfireServer();
        app.UseHangfireDashboard();

        //Add the recurring job
        RecurringJob.AddOrUpdate<IScheduledTaskManager>(task => task.ProcessDailyJob(), Cron.Daily);

        //app.UseMvc...
        //...other code
    }

    public IServiceProvider ConfigureServices(IServiceCollection services) {    
        // Adding custom services
        services.AddTransient<IScheduledTaskManager, ScheduledTaskManageImplementation>();
        //add other dependencies...

        // add hangfire services
        services.AddHangfire(x => x.UseSqlServerStorage("<connection string>"));

        //configure Autofac
        this.ApplicationContainer = getWebAppContainer(services);
        //get service provider    
        return new AutofacServiceProvider(this.ApplicationContainer);
    }

    IContainer getWebAppContainer(IServiceCollection service) {
        var builder = new ContainerBuilder();        
        builder.RegisterModule(new BusinessBindingsModule());
        builder.RegisterModule(new DataAccessBindingsModule());
        builder.Populate(services);
        return builder.Build();
    }        


    //...other code
}

References

Hangfire 1.6.0

Integrate HangFire With ASP.NET Core

Using IoC containers

Moyer answered 29/5, 2017 at 11:50 Comment(4)
Here is what I'm still not understanding: if I only add the app.UseHangfireServer() line of code, why is it firing ConfigureServices()?Cutlip
@im1dermike In the start up sequence ConfigureServices() is always called before Configuration Take a look here where they explain it. learn.microsoft.com/en-us/aspnet/core/fundamentals/…Moyer
@im1dermike According to hangfire documentation you HAVE to call AddHangfire in ConfigureServices before calling app.UseHangfireServer(). I checked they source code on GitHub and saw as such as well. Have you looked at the links I provided in the question?Moyer
It seems like I need to resolve services.AddScoped<IPrincipal>(...) differently for the web app vs. Hangfire. It seems like I'd want a different body of ConfigureServices() for the web app thread and the Hangfire thread, no?Cutlip
A
1

Why is Hangfire trying to resolve the .NET Core Startup class?

Hangfire doesn't store lambda expressions in the database, it stores the type and method being called. Then when the scheduled task is due to run, it resolves the type from the container and calls the method.

In your case, the method is on Startup.

You can register Startup with Autofac if you want, but it's probably easiest to have a scheduled task service:

AddOrUpdate<IScheduledTaskService>(x => x.DailyTask(), Cron.Daily);
Akiko answered 24/5, 2017 at 13:28 Comment(1)
Can you please help me with this question : #49514599Venusberg
O
0

I'm not sure of the type for jobmanager off the top of my head, but you can resolve the dependency from the container using a scope. You'll want to resolve from the scope in a using statement to prevent memory leaks. See the Autofac Docs

// not sure what type "jobManager" is
TYPE jobManager;

using(var scope = ApplicationContainer.BeginLifetimeScope())
{
    jobManager = scope.Resolve<TYPE>();
}

RecurringJob.AddOrUpdate( ... );
Obsecrate answered 18/5, 2017 at 23:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.