Search code examples
c#autofac

Given a ContainerBuilder, can I register a missing dependency handler?


I am trying to make a method with the following signature:

void Chain(ContainerBuilder builder, IServiceProvider fallbackServiceProvider)
{
   // ...
}

The idea is that Chain can be used as follows:

IServiceProvider fallbackProvider = someExternalProvider;
var builder = new ContainerBuilder();

// Custom registration might happen before and/or after the call to Chain
builder.Register<MyCustomService>().As<IMyCustomService>();
builder.Register<MyExternalServiceReplacement>.As<IExternalService>();

Chain(builder, someExternalProvider);

IContainer container = builder.Build();

// customService should be a MyCustomService
var customService = container.Resolve<IMyCustomService>();

// replacedService should be overridden by MyExternalServiceReplacement
// even though an IExternalService also exists in someExternalProvider
var replacedService = container.Resolve<IExternalService>();

// nonReplacedService should come from someExternalProvider since
// no IExternalService2 was registered with the ContainerBuilder
var nonReplacedService = container.Resolve<IExternalService2>();

Ideally there would be some type of missing dependency handler that I could register with the ContainerBuilder.

Alternatively, I could probably get by with some way to register a component that could intercept every call to Resolve*, TryResolve*, etc... This would also need to intercept the dependency resolution for constructor injection.

Unfortunately, there is no way to query the IServiceProvider to get every service it provides. I can only call into the object IServiceProvider.GetService(Type serviceType) method of the fallbackServicProvider.


Solution

  • You need a custom IRegistrationSource implementation: when the container needs to provide a service, it queries the registered registration sources to get any available implementations.

    So inside the registeration source you can ask your IServiceProvider to give you a fallback implementation for a given type.

    Here is a good article intoroducing the whole registration source in Autofac: Declarative Context Adapters in Autofac 2

    Based on that I've knocked together a prototype IRegistrationSource implementation (so it is not fully tested nor production ready, but it was working with your sample scenario) what you can build on:

    public class MyRegistrationSource : IRegistrationSource
    {
        private readonly IServiceProvider serviceProvider;
    
        public MyRegistrationSource(IServiceProvider serviceProvider)
        {
            this.serviceProvider = serviceProvider;
        }
    
        public IEnumerable<IComponentRegistration> RegistrationsFor(Service service,
            Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
        {
            // there are other registration exists in the container
            if (registrationAccessor(service).Any())
                return Enumerable.Empty<IComponentRegistration>();
    
            var swt = service as IServiceWithType;
            if (swt == null)
                return Enumerable.Empty<IComponentRegistration>();
    
            // try to get an instance from the IServiceProvider
            var instance = serviceProvider.GetService(swt.ServiceType);
            if (instance == null)
                return Enumerable.Empty<IComponentRegistration>();
    
            // register the instance in the container
            return new[]
                {
                    RegistrationBuilder.ForDelegate(swt.ServiceType, 
                        (c, p) => instance)
                        .CreateRegistration()
                };
        }
        public bool IsAdapterForIndividualComponents { get { return false; } }
    }
    

    And you can use it like this:

     var builder = new ContainerBuilder();
    
     // Custom registration might happen before and/or after the call to Chain
     builder.RegisterType<MyCustomService>().As<IMyCustomService>();
     builder.RegisterType<MyExternalServiceReplacement>().As<IExternalService>();
    
     //Chain(builder, someExternalProvider);
     builder.RegisterSource(new MyRegistrationSource(new ServiceProvider()));
    
     IContainer container = builder.Build();