6

I have an ASP.NET MVC web application using Autofac for dependency injection. Occasionally, this web application will start a thread to do some work separate from the request thread. When this background thread starts up, it establishes a new Autofac lifetime scope from the root container and runs some action.

public IAsyncResult Run<T>(Action<T> action)
{
    var NewTask = System.Threading.Tasks.Task.Factory.StartNew(() =>
    {
        using (var Scope = Runtime.Container.BeginLifetimeScope())
        {
            var Input = Scope.Resolve<T>();
            action(Input);
        }
    });

    return NewTask;
}

One of my dependencies registered with Autofac has two different implementations: one appropriate for http-request lifetimes and another appropriate for all other lifetimes. I tried to register them as follows:

builder
.Register(c => new Foo())
.As<IFoo>()
.InstancePerLifetimeScope();

builder
.Register(c => new FooForHttp(HttpContext.Current))
.As<IFoo>()
.InstancePerMatchingLifetimeScope(WebLifetime.Request);

Autofac selects FooForHttp for http requests (as expected). However, when my background thread spins up, any attempt to resolve IFoo results in an exception:

No scope with a Tag matching 'httpRequest' is visible from the scope in which the instance was requested. This generally indicates that a component registered as per-HTTP request is being reqested 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.

I know that Autofac always uses the last registered provider as the default provider for a particular component. I made an assumption here that it would use the last registered suitable provider.

Am I missing something, or is there a better approach to selecting a provider based on the tag of the current lifetime scope?

trailmax
  • 34,305
  • 22
  • 140
  • 234
Michael Petito
  • 12,891
  • 4
  • 40
  • 54

2 Answers2

7

Register the web Foo as normal, but don't register the other Foo. When creating the lifetime scope for the async task, use the overload of BeginLifetimeScope() that takes an action on ContainerBuilder. Register the background Foo in this action (b => b.Register()) and this should override the web one. (Small keyboard here sorry :))

Nicholas Blumhardt
  • 30,271
  • 4
  • 90
  • 101
  • Thanks -- I missed that overload for `BeginLifetimeScope` and that was exactly what I needed! – Michael Petito Dec 29 '11 at 03:33
  • FYI: There is a similar constructor overload for `Autofac.Integration.Web.ContainerProvider` that accepts the same action. I decided to provide the override from that end, since the override is specific to web requests. – Michael Petito Dec 29 '11 at 03:54
  • What is the point of tags if you have to do custom registration for the scope anyway? Then the tag could just be omitted, as far as I understand. I would really like this tag feature to be able to select between two registration with different tags, but it appears the last tagged registrations overrides the previous which I just can't wrap my head around. What is the point of the tag then? – nietras Nov 12 '13 at 10:58
  • @harrydev I just ran into this problem again yesterday and, while custom registrations per scope work, there is another potential solution proposed here http://stackoverflow.com/questions/14672541/instance-per-matching-lifetime-scope-with-default/14722537#14722537 that extends Autofac with a new component lifetime `MatchingScopeOrRootLifetime`. If you only need to return a default singleton instance when the current lifetime scope doesn't match (or exist), this approach may help you too. – Michael Petito Nov 12 '13 at 13:47
  • @MichaelPetito Thanks. I had seen this, but sadly no it does not really help me, I actually have more than 2 different cases to select between, so was hoping you could choose between these by simply registrering them with different tags e.g. "A", "B", "C" and then create a tagged lifetime scope e.g. with "A" and then the registrations for this would be used for all components registrered as PerMatchingLifetimeScope... – nietras Nov 12 '13 at 14:40
1

This can also be solved by using a tagged life time scope. Register your fisrt Foo as instance of your tagged scope:

builder.RegisterType<Foo>().As<IFoo>.InstancePerMatchingLifetimeScope("YourScopeTag");

And create the scope with the same tag you registered your dependencie:

  using (var Scope = Runtime.Container.BeginLifetimeScope("YourScopeTag"))
        {
            var Input = Scope.Resolve<T>();
            action(Input);
        }

Haven't tested it, but it should work

http://docs.autofac.org/en/latest/lifetime/instance-scope.html#instance-per-matching-lifetime-scope

andyroschy
  • 499
  • 3
  • 11