Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I register a (boundless) type hierarchy using Autofac?

Tags:

autofac

I've got a Factory interface (along with concrete implementations):

// foo.dll
interface IFooProvider
{
    T GetFoo<T>()
        where T : BaseFoo;
}

My BaseFoo is not abstract, but only its subclasses are actually useful:

// shared.dll
class BaseFoo
{ ... }

I've also got a (potentially unbounded) number of subclasses of BaseFoo across many assemblies:

// foo.dll
class AFoo : BaseFoo
{ ... }

// foo2.dll
class BFoo : BaseFoo
{ ... }

... and many more ...

Naively, I had been registering the Foo-derived classes in an unsurprising way:

// foo.dll
class ConcreteFooRegistration : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        // a concrete FooProvider is registered elsewhere

        builder.Register(c => c.Resolve<IFooProvider>().GetFoo<AFoo>());
        builder.Register(c => c.Resolve<IFooProvider>().GetFoo<BFoo>());
        ...
    }
}

But this implies that:

  1. the assembly containing ConcreteFooRegistration (e.g. foo.dll) also contains some/all of AFoo, BFoo, etc.
  2. the assembly containing ConcreteFooRegistration (e.g. foo.dll) references the assemblies (e.g. foo2.dll) containing some/all of AFoo, BFoo, etc.
  3. IFooProvider be available to any other assembly containing BaseFoo-derived classes and the Module that registers them

For sake of discussion, assume that none of these is possible and/or desirable. That is, I'm looking for solutions other than "move IFooProvider into shared.dll".

Since AFoo and BFoo are the real dependencies that other types are interested in, and IFooProvider is (from that perspective) just an instantiation detail, I got inspired by the Autofac+Serilog integration that Nicholas came up with. I've used a similar approach elsewhere, so I wrote up an AttachToComponentRegistration() implementation:

// foo.dll
class ConcreteFooRegistration : Module
{
    // NOTICE: there's no Load() method
    protected override void AttachToComponentRegistration(...)
    {
        ...
        registration.Preparing += (sender, e) =>
        {
          var pFoo = new ResolvedParameter(
              (p, i) => p.ParameterType.IsAssignableTo<BaseFoo>(),
              (p, i) => i.Resolve<IFooProvider>().GetFoo<FooWeNeed>()
          );
          e.Parameters = new [] { pFoo }.Concat(e.Parameters);
        };
    }
}

This was successful, in that I was able to remove all the individual BaseFoo-derived registrations from ConcreteFooRegistration and still successfully resolve arbitrary BaseFoo-derived dependencies with constructor injection:

// other.dll:
class WorkerRegisteration : Module
{
   protected override void Load(ContainerBuilder builder)
   {
       builder.RegisterType<Worker>();
       // NOTICE: FooYouDidntKnowAbout is NOT explicitly registered
   }
}

class Worker
{
   public Worker(FooYouDidntKnowAbout foo)
   { ... }

   ...
}

BUT: now I can't arbitrarily resolve AFoo outside of constructor injection:

builder.Register(c =>
{
    // here's one use for a BaseFoo outside constructor injection
    var foo = c.Resolve<AFoo>();
    if (foo.PropValue1)
        return new OtherClass(foo.PropValue2);
    else
        return new YetAnother(foo.PropValue3);
}
...
builder.Register(c =>
{
    // here's another
    var foo = c.Resolve<AFoo>();
    return c.Resolve(foo.TypePropValue);
});

Assuming that publishing IFooProvider as a public export of foo.dll or moving it to shared.dll is undesirable/impossible, thus eliminating the naive-but-unsurprising implementation above, (how) can I set up my registrations to be able to resolve arbitrary subclasses of BaseFoo from anywhere?

Thanks!

like image 666
David Rubin Avatar asked Dec 14 '25 06:12

David Rubin


1 Answers

I think what you're looking for is a registration source. A registration source is a dynamic "registration provider" you can use to feed Autofac registrations as needed.

As of this writing, the doc on registration sources is pretty thin (I just haven't gotten a chance to write it) but there's a blog article with some details about it.

Registration sources are how Autofac supports things like IEnumerable<T> or Lazy<T> - we don't require you actually register every collection, instead we dynamically feed the registrations into the container using sources.

Anyway, let me write you up a sample here and maybe I can use it later to massage it into the docs, eh? :)

First, let's define a very simple factory and implementation. I'm going to use "Service" instead of "Foo" here because my brain stumbles after it sees "foo" too many times. That's a "me" thing. But I digress.

public interface IServiceProvider
{
    T GetService<T>() where T : BaseService;
}

public class ServiceProvider : IServiceProvider
{
    public T GetService<T>() where T : BaseService
    {
        return (T)Activator.CreateInstance(typeof(T));
    }
}

OK, now let's make the service types. Obviously for this sample all the types are sort of in one assembly, but when your code references the type and the JIT brings it in from some other assembly, it'll work just the same. Don't worry about cross-assembly stuff for this.

public abstract class BaseService { }
public class ServiceA : BaseService { }
public class ServiceB : BaseService { }

Finally, a couple of classes that consume the services, just so we can see it working.

public class ConsumerA
{
    public ConsumerA(ServiceA service)
    {
        Console.WriteLine("ConsumerA: {0}", service.GetType());
    }
}


public class ConsumerB
{
    public ConsumerB(ServiceB service)
    {
        Console.WriteLine("ConsumerB: {0}", service.GetType());
    }
}

Good.

Here's the important bit, now: the registration source. The registration source is where you will:

  1. Determine if the resolve operation is asking for a BaseService type or not. If it's not, then you can't handle it so you'll bail.
  2. Build up the dynamic registration for the specific type of BaseService derivative being requested, which will include the lambda that invokes the provider/factory to get the instance.
  3. Return the dynamic registration to the resolve operation so it can do the work.

It looks like this:

using Autofac;
using Autofac.Core;
using Autofac.Core.Activators.Delegate;
using Autofac.Core.Lifetime;
using Autofac.Core.Registration;

public class ServiceRegistrationSource : IRegistrationSource
{
    public IEnumerable<IComponentRegistration> RegistrationsFor(
        Service service,
        Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
    {
        var swt = service as IServiceWithType;
        if(swt == null || !typeof(BaseService).IsAssignableFrom(swt.ServiceType))
        {
            // It's not a request for the base service type, so skip it.
            return Enumerable.Empty<IComponentRegistration>();
        }

        // This is where the magic happens!
        var registration = new ComponentRegistration(
            Guid.NewGuid(),
            new DelegateActivator(swt.ServiceType, (c, p) =>
            {
                // The factory method is generic, but we're working
                // at a reflection level, so there's a bit of crazy
                // to deal with.
                var provider = c.Resolve<IServiceProvider>();
                var method = provider.GetType().GetMethod("GetService").MakeGenericMethod(swt.ServiceType);
                return method.Invoke(provider, null);
            }),
            new CurrentScopeLifetime(),
            InstanceSharing.None,
            InstanceOwnership.OwnedByLifetimeScope,
            new [] { service },
            new Dictionary<string, object>());

        return new IComponentRegistration[] { registration };
    }

    public bool IsAdapterForIndividualComponents { get{ return false; } }
}

It looks complex, but it's not too bad.

The last step is to get the factory registered as well as the registration source. For my sample, I put those in an Autofac module so they're both registered together - it doesn't make sense to have one without the other.

public class ServiceProviderModule : Autofac.Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<ServiceProvider>().As<IServiceProvider>();
        builder.RegisterSource(new ServiceRegistrationSource());
    }
}

Finally, let's see it in action. If I throw this code into a console app...

static void Main()
{
    var builder = new ContainerBuilder();
    builder.RegisterType<ConsumerA>();
    builder.RegisterType<ConsumerB>();
    builder.RegisterModule<ServiceProviderModule>();
    var container = builder.Build();

    using(var scope = container.BeginLifetimeScope())
    {
        var a = scope.Resolve<ConsumerA>();
        var b = scope.Resolve<ConsumerB>();
    }
}

What you end up with on the console is:

ConsumerA: ServiceA
ConsumerB: ServiceB

Note I had to register my consuming classes but I didn't explicitly register any of the BaseService-derived classes - that was all done by the registration source.

If you want to see more registration source samples, check out the Autofac source, particularly under the Autofac.Features namespace. There you'll find things like the CollectionRegistrationSource, which is responsible for handling IEnumerable<T> support.

like image 133
Travis Illig Avatar answered Dec 16 '25 03:12

Travis Illig



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!