Search code examples
c#hangfire

Activating classes with hangfire in ASP Net Core


I have issues with configuring HangFire. I'm trying to fire and forget an alarming call in ASP Net Core.

Alarming Service:

public void FireAndForgetAlarm(int statusCode, string message, string subject = null, IList<string> attachmentFilePaths = null)
{
    BackgroundJob.Enqueue(() => Alarm(statusCode, message, subject, 
    attachmentFilePaths));
}

*The Alarm method is public.

Error description:

warn: Hangfire.AutomaticRetryAttribute[0] Failed to process the job 'eb7f76ea-7522-49e2-b7b9-3ed3ed091a0c': an exception occurred. Retry attempt 2 of 10 will be performed in 00:01:02. System.InvalidOperationException: Unable to resolve service for type 'Wms.Alarming.AlarmingConfig' while attempting to activate 'Wms.Alarming.AlarmingService'. at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider) at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters) at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetServiceOrCreateInstance(IServiceProvider provider, Type type) at Hangfire.AspNetCore.AspNetCoreJobActivatorScope.Resolve(Type type) at Hangfire.Server.CoreBackgroundJobPerformer.Perform(PerformContext context) at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass9_0.b__0() at Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter filter, PerformingContext preContext, Func1 continuation) at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass9_1.<PerformJobWithFilters>b__2() at Hangfire.Server.BackgroundJobPerformer.PerformJobWithFilters(PerformContext context, IEnumerable1 filters) at Hangfire.Server.BackgroundJobPerformer.Perform(PerformContext context) at Hangfire.Server.Worker.PerformJob(BackgroundProcessContext context, IStorageConnection connection, String jobId)

My startup class contains:

public void ConfigureServices(IServiceCollection services)
{
    services.ConfigureAlarmingServices(new AlarmingConfig()
    {
        Port = int.Parse(ConfigurationManager.AppSettings["mailPort"]), // for SSL 465
        Sender = ConfigurationManager.AppSettings["mailSender"],
        SmtpServer = ConfigurationManager.AppSettings["smtpServer"],
        Recipients = ConfigurationManager.AppSettings["mailRecipients"],
        SenderPassword = ConfigurationManager.AppSettings["mailPassword"]
    });
    services.AddHangfire(c => {
            c.UseMemoryStorage();
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 
{
     app.UseHangfireServer(new BackgroundJobServerOptions(){Activator = new AspNetCoreJobActivator(app.ApplicationServices.GetRequiredService<IServiceScopeFactory>())});
}

Notes:

  • I have tried multiple versions for the startup class, including putting the activator in the AddHangire method. No attempt actually worked, and I followed the documentation properly too.
  • The alarming service is properly configured, it works without HangFire.
  • Passing literals in the alarming config does not work
  • Only solution is making the method static, but this does not allow for nice DI
  • I have seen a solution in their documentation where they would always have a default constructor and call the constructor with paramters. However, this is not possible for me as I'm also injecting the logger.

Content of extension method as requested in comments:

public static void ConfigureAlarmingServices(this IServiceCollection collection, AlarmingConfig config)
{
    collection.AddSingleton<IAlarmingService>(x=> new 
    AlarmingService(x.GetService<ILogger<IAlarmingService>>(), config));
}

Solution

  • First off, the important part of the exception you are seeing is:

    Unable to resolve service for type 'Wms.Alarming.AlarmingConfig' while attempting to activate 'Wms.Alarming.AlarmingService'

    Which indicates that somewhere in your code (a part you haven't shown yet) you are trying to inject an instance of AlarmingService.

    You've configured how to resolve a singleton service for the interface IAlarmingService, which means you should be able to have a class depend on IAlarmingService. However, if your class depends specfically on AlarmingService (not the interface), you will get this error, because you have not defined how to resolve a dependency of type AlarmingService.

    Either...

    • the code you haven't shown should only depend on IAlarmingService and not the concrete class itself (AlarmingService) or..
    • you need to also configure DI container to handle a dependency specifically of type AlarmingService, e.g.:
    public static void ConfigureAlarmingServices(this IServiceCollection collection, AlarmingConfig config)
    {
        collection.AddSingleton<AlarmingService>(
            x => new AlarmingService(x.GetService<ILogger<AlarmingService>>(), config));
        collection.AddSingleton<IAlarmingService>(x => x.GetService<AlarmingService>());
    }
    

    I think the first point is what you need to fix.