Autofac job activator with Hangfire throwing exception DependencyResolutionException

5k views Asked by At

My project structure is same as :

https://github.com/MarlabsInc/webapi-angularjs-spa

I have followed the instructions in :

http://docs.hangfire.io/en/latest/background-methods/using-ioc-containers.html

So I have created a container job activator.

In my Bootstrapper.cs

containerBuilder.RegisterType<DatabaseFactory>().As<IDatabaseFactory>().AsImplementedInterfaces().InstancePerApiRequest();
containerBuilder.RegisterType<UnitOfWork>().As<IUnitOfWork>().AsImplementedInterfaces().InstancePerApiRequest();
containerBuilder.RegisterApiControllers(System.Reflection.Assembly.GetExecutingAssembly());
IContainer container = containerBuilder.Build();

Hangfire.GlobalConfiguration.Configuration
.UseAutofacActivator(container);
JobActivator.Current = new AutofacJobActivator(container);
System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);

My startup class has a method :

GlobalConfiguration.Configuration
.UseSqlServerStorage("entitiesDB",
new SqlServerStorageOptions
{
    PrepareSchemaIfNecessary = false,
    InvisibilityTimeout = TimeSpan.FromMinutes(30)
});

app.UseHangfireDashboard();
app.UseHangfireServer();

In controller : I am trying to update the status of 2000 invoices as "Approved" So the method is straightforward as follows :

foreach(int id in invoiceIds)
{
   BackgroundJob.Enqueue<IInvoiceService>(a => a.UpdateInvoice(id));
}

Now when I query in the SQL :

 select * from HangFire.[State]

I get the following exception in Data column:

{"FailedAt":"2015-07-07T10:00:40.9454943Z","ExceptionType":"Autofac.Core.DependencyResolutionException","ExceptionMessage":"No scope with a Tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested. This generally indicates that a component registered as per-HTTP request is being requested by a SingleInstance() component (or a similar scenario.) Under the web integration always request dependencies from the DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime, never from the container itself.","ExceptionDetails":"Autofac.Core.DependencyResolutionException: No scope with a Tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested. This generally indicates that a component registered as per-HTTP request is being requested by a SingleInstance() component (or a similar scenario.) Under the web integration always request dependencies from the DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime, never from the container itself.\r\n at Autofac.Core.Lifetime.MatchingScopeLifetime.FindScope(ISharingLifetimeScope mostNestedVisibleScope)\r\n at Autofac.Core.Resolving.InstanceLookup..ctor(IComponentRegistration registration, IResolveOperation context, ISharingLifetimeScope mostNestedVisibleScope, IEnumerable1 parameters)\r\n at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, IComponentRegistration registration, IEnumerable1 parameters)\r\n at Autofac.Core.Resolving.ResolveOperation.ResolveComponent(IComponentRegistration registration, IEnumerable1 parameters)\r\n at Autofac.Core.Resolving.ResolveOperation.Execute(IComponentRegistration registration, IEnumerable1 parameters)\r\n at Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(IComponentRegistration registration, IEnumerable1 parameters)\r\n at Autofac.Core.Container.ResolveComponent(IComponentRegistration registration, IEnumerable1 parameters)\r\n at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable1 parameters, Object& instance)\r\n at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable1 parameters)\r\n at Autofac.ResolutionExtensions.Resolve(IComponentContext context, Type serviceType, IEnumerable`1 parameters)\r\n at Autofac.ResolutionExtensions.Resolve(IComponentContext context, Type serviceType)\r\n at Hangfire.AutofacJobActivator.ActivateJob(Type jobType)\r\n at Hangfire.Common.Job.Activate(JobActivator activator)"}

Can someone please help me understand what I am doing wrong ?

1

There are 1 answers

9
Ergwun On

You are using InstancePerApiRequest so your IoC container knows to create a new instance for each API request.

But, your background job is not executing inside an API request, so your IoC container does not know how to resolve your dependencies.

From the hangfire docs:

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.

According to this related SO question, Autofac does not support registering for multiple scopes out of the box so you will need to either:

  1. Use a "lower" scope such as "InstancePerDependency", or
  2. Use a separate IoC container for background jobs.

UPDATE:

The Hangfire Autofac integration package introduces a InstancePerBackgroundJob() extension method, and suggests that Autofac does support registering for multiple scopes like this:

builder.RegisterType<Database>()
       .InstancePerBackgroundJob()
       .InstancePerHttpRequest();

However, it's only available in the 1.2.0-beta1 release of Hangfire.Autofac which requires Hangfire 1.5.0-beta1.