3

I am using Autofac version 3.5.2.

I want to register a singleton class A that needs B until some point in the future. How do you do this if B is registered using InstancePerRequest?

Using Owned<B> directly does not work because the per request lifetime scope does not exist. You get the DependencyResolutionException as seen here.

One solution is to make A have a direct dependency on ILifetimeScope. A begins a scope with the MatchingScopeLifetimeTags.RequestLifetimeScopeTag when it needs an instance of B.

    using (var scope = this.lifetimeScope.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag))
    {
        var b = scope.Resolve<B>();
        b.DoSomething();
    }

I do not like this approach since it is the service locator pattern. See this fiddle.

A second solution is to register B as InstancePerRequest and in the correct owned scope for B. Registering B would look like this: builder.RegisterType<B>().InstancePerRequest(new TypedService(typeof(B))); The full example is here.

I like the second solution better but it also has problems:

  1. It feels like a code smell that when registering B’s dependencies that they have to know to register themselves in B’s owned scope.

  2. It does not scale well when B has a lot of dependencies and those dependencies have dependencies etc. All these dependencies need the extra registration as in problem 1 above.

How would you recommend solving this problem? Thanks for the help.

Update

I have moved the OwnedPerRequest<T> related code into this answer below.

Community
  • 1
  • 1
Landon
  • 76
  • 6

3 Answers3

1

This answer is based on Nicholas' comment and his blog post on IRegistrationSource

It is based on the existing Owned<T> class and its registration source.

public class OwnedPerRequest<T> : Owned<T>
{
    public OwnedPerRequest(T value, IDisposable lifetime) : base(value, lifetime) { }
}

public class OwnedPerRequestInstanceRegistrationSource : IRegistrationSource
{
    public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
    {
        if (service == null)
            throw new ArgumentNullException(nameof(service));
        if (registrationAccessor == null)
            throw new ArgumentNullException(nameof(registrationAccessor));

        var swt = service as IServiceWithType;
        if (swt == null
             || !(swt.ServiceType.IsGenericType
                    && swt.ServiceType.GetGenericTypeDefinition() == typeof(OwnedPerRequest<>)))
            return Enumerable.Empty<IComponentRegistration>();

        var ownedInstanceType = swt.ServiceType.GetGenericArguments()[0];
        var ownedInstanceService = swt.ChangeType(ownedInstanceType);

        return registrationAccessor(ownedInstanceService)
            .Select(r =>
            {
                var rb = RegistrationBuilder.ForDelegate(swt.ServiceType, (c, p) =>
                {
                    var lifetime = c.Resolve<ILifetimeScope>().BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag);
                    try
                    {
                        var value = lifetime.ResolveComponent(r, p);
                        return Activator.CreateInstance(swt.ServiceType, value, lifetime);
                    }
                    catch
                    {
                        lifetime.Dispose();
                        throw;
                    }
                });

                return rb
                    .ExternallyOwned()
                    .As(service)
                    .Targeting(r)
                    .CreateRegistration();
            });
    }

    public bool IsAdapterForIndividualComponents => true;

    public override string ToString() => "OwnedPerRequestInstanceregistrationSource";
}
Community
  • 1
  • 1
Landon
  • 76
  • 6
0

I think what you want to do is inject a Func<Owned<B>> which you invoke when you need a B. This removes the service locator pattern and I'm pretty sure is identical to this in functionality to this

using (var scope = this.lifetimeScope.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag))
{
    var b = scope.Resolve<B>();
    b.DoSomething();
}

If you inject a Func<Owned<B>> the usage would look like this:

public void DoSomethingThatUsesB()
{
    //_bFactory is your Func<Owned<B>>
    using(var b = _bFactory.Invoke())
    {
         ... (use b)
    }
}
ivanPfeff
  • 128
  • 1
  • 8
  • Is [this](https://dotnetfiddle.net/XxGKlk) what you had in mind? If so, it throws the `DependencyResolutionException`. – Landon Aug 26 '16 at 13:03
  • The `DoSomethingThatUsesB` should only be invoked inside of a web request if it is registered as `InstancePerRequest` (InstancePerRequest is typically used in web applications), I think you want to register the B as `InstancePerLifetimeScope` which should resolve your issue – ivanPfeff Aug 26 '16 at 13:33
0

If the lifetime structure of your app is quite simple (i.e. you don't nest further lifetime scopes under "request" that need to resolve the shared B), then you might just switch B to InstancePerLifetimeScope():

builder.RegisterType<B>().InstancePerLifetimeScope();

This will be largely equivalent to per-request, but will also allow the Owned<B> to be successfully resolved by a singleton.

The only caveat is that you have to be careful not to accidentally take a dependency on B from singletons elsewhere, since this will no longer be detected as an error. It's possible to protect against this using a custom IComponentLifetime but probably not worth the effort unless you do it a lot.

Nicholas Blumhardt
  • 30,271
  • 4
  • 90
  • 101