0

I'm trying to port a working Hangfire setup embedded in a Kestrel webserver to a console app. I've modified the web app so it still provides the Hangfire dashboard but doesn't start its own Hangfire server.

The code I must port uses Autofac. I've added the Hangfire.Autofac package to the console app and have already performed all the steps detailed in the answer to Hangfire Autofac .net core 3.1

When I create a job (using the web app) the console app Hangfire server tries to execute the job but I get this failure message:

The requested service 'AED.ServicesLayer.JobProcessing.ProcessManager' has not been registered.

Investigating this we examine the setup of Autofac in the console app. This is how I set up my container.

IConfiguration config = new ConfigurationBuilder()
  .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
  .Build();
var containerBuilder = new Autofac.ContainerBuilder();
containerBuilder.RegisterInstance(Log.Logger).AsImplementedInterfaces();
containerBuilder.RegisterModule(new RepositoryModule(config));
containerBuilder.RegisterType<UserService>().As<IUserService>();
containerBuilder.RegisterInstance(config).As<IConfiguration>();
containerBuilder.RegisterModule(new JobProcessingModule(config));
var container = containerBuilder.Build();

When the app is executed, hitting a breakpoint in JobProcessingModule proves the following line of code is executed.

builder.RegisterType<ProcessManager>().As<IProcessManager>();

It is very curious that the containerBuilder instance passed to JobProcessingModule.Load(containerBuilder) is not the same containerBuilder object on which RegisterModule is invoked.

However, experiments with simplified injectables suggest that this is normal, and the injected items are nevertheless visible in the registrations for the container that is returned.

Re-examining the logged failure we note that the class is mentioned by class name and not by interface. Changing the registration by removing the interface registration, like so

builder.RegisterType<ProcessManager>();//.As<IProcessManager>();

caused the ProcessManager to be found in the Hangfire console host but caused run-time errors in the web application when creating the job.

Registering it both ways caused ProcessManager to be found by both, with a new problem surfacing: cannot resolve dependencies. This, however, is merely a new case of the same problem.

While this allows me to move forward with getting a console host working, I do not like code I do not understand. Why should the console host require registration by class name when the web app does not?

Whatever is causing this has also caused Hangfire.IBackgroundJobClient to fail to resolve to the background job client. This is a hangfire class so it really does seem like there is a fundamental problem.

Peter Wone
  • 17,965
  • 12
  • 82
  • 134
  • I am guessing most likely you are doing the property injection, can you check if you replace that by field, does your code injects, also check this link, which is about asp.net mvc but solutions covers what to do when doing autofac property injection which needs some additional code https://stackoverflow.com/a/51982317/1559611 – Mrinal Kamboj Mar 27 '21 at 01:42
  • You are correct that property injection is the purpose of using Autofac. I do not understand what you mean by "if you replace that by field". Replace what with a field? – Peter Wone Mar 27 '21 at 03:05
  • I mean Property Injection would look out for a property to inject, does it help if you can use a field instead, does the injection happens, it was just for the Trial purpose – Mrinal Kamboj Mar 27 '21 at 13:14
  • Something is requesting `ProcessManager` instead of `IProcessManager`. Are you sure that all your services has dependencies over `IProcesManager` and not `ProcessManager ? Could you share the stack trace of the exception to understand who is requesting this service? – Cyril Durand Mar 29 '21 at 13:38
  • The exception is captured and swallowed by Hangfire in non-steppable code. I have just the message from the log. – Peter Wone Mar 31 '21 at 00:01

1 Answers1

0

A lengthy investigation eventually revealed, confirmed by experiments, that this code

_recurringJobManager.AddOrUpdate(
  insertResult.ToString(), 
  pm => pm.RunScheduledJobs(insertResult), interval.CrontabExpression
);

is responsible for the behaviour described in the question. AddOrUpdate is a generic method. When it is not explicitly typed it acquires its type from the class of the object passed to it. When the method is explicitly typed as the interface, like so

_recurringJobManager.AddOrUpdate<IProcessManager>(
  insertResult.ToString(), 
  pm => pm.RunScheduledJobs(insertResult), interval.CrontabExpression
);

it remains compatible with the object, but the type acquired by Hangfire is the interface, and the console application can resolve ProcessManager from its interface.

Why the problem was not manifest in the web hosted Hangfire server remains a puzzle, but at least now I'm puzzled by the absence of a problem in a situation I don't have.

Peter Wone
  • 17,965
  • 12
  • 82
  • 134