Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keyed delegate factories with runtime constructor parameters?

Tags:

autofac

Lets say I have the following service and components:

public interface IService
{
    void DoWork();
}

public class ServiceA : IService
{
    private readonly string _name;

    public ServiceA(string name)
    {
        _name = name;
    }

    public void DoWork()
    {
        //ServiceA DoWork implementation
    }
}

public class ServiceB : IService
{
    private readonly string _name;

    public ServiceB(string name)
    {
        _name = name;
    }

    public void DoWork()
    {
        //ServiceB DoWork implementation
    }
}

Notice that each component takes a constructor parameter name. Lets also say that name is determined at runtime.

I've been going through the AutoFac documentation to try to find a type-safe way to resolve components like this without having to reference the container directly. If I had only one implementation of IService then I could just use a Delegate Factory to pass the runtime parameter to the constructor. However, I have two implementations, and the one that should be used must also be determined at runtime. If I didn't have the name constructor parameter then I could register the two components by Key and the resolve using IIndex

I can't figure out how to do both. Is there way I can somehow combine the use of Delegate Factories and IIndex component resolution? Or is there another way to register and resolve both components without having to directly reference the container?

like image 858
Kerby Avatar asked Jul 28 '14 19:07

Kerby


2 Answers

As you say, your two individual requirements are natively supported by AutoFac.

  • Runtime resolution of constructor parameters can be implemented using Parameterized Instantiation
  • Resolution of a particular service implementation can be implemented using Keyed Service Lookup

However, there doesn't appear to be direct support for using these two constructs together. I.e. the following does not work:

public enum ServiceType
{
    ServiceA,
    ServiceB
}

public class MyComponent
{
    public MyComponent(Func<string, IIndex<ServiceType, IService> factory)
    {
        var service = factory("some_string")[ServiceType.ServiceA];
    }
}

My work around for this has always been to move the resolution of the service to a factory per service implementation. This then works as follows:

  1. Components with a dependency on a specific service implementation take in an AutoFac factory delegate that resolves to the factory specific to the required service implementation
  2. In turn, the service factories have a dependency on an AutoFac factory delegate that knows how to create the specific service implementation from the (runtime) constructor parameters of the service
  3. This approach uses the native AutoFac constructs and doesn't have any dependencies on AutoFac outside of the container wiring.

Heres a rough-and-ready example. Note the multiple factories could be reduced to a single generic factory - but I've left it as-is for clarity:

Service implementations

public enum ServiceType
{
    NotSet,
    ServiceA,
    ServiceB
}

public interface IService
{
    string DoWork();
}

public class ServiceA : IService
{
    private readonly string _name;

    public ServiceA(string name)
    {          
        _name = name;
    }

    public string DoWork()
    {
        throw new NotImplementedException();
    }
}

public class ServiceB : IService
{
    private readonly string _name;

    public ServiceB(string name)
    {           
        _name = name;
    }

    public string DoWork()
    {
        throw new NotImplementedException();
    }
}

Service factories

public interface IServiceFactory
{
    IService Create(string name);
}

public class ServiceAFactory : IServiceFactory
{
    private readonly Func<string, ServiceA> _factory;

    public ServiceAFactory(Func<string, ServiceA> factory)
    {            
        _factory = factory;
    }

    public IService Create(string name)
    {
        return _factory(name);
    }
}

public class ServiceBFactory : IServiceFactory
{
    private readonly Func<string, ServiceB> _factory;

    public ServiceBFactory(Func<string, ServiceB> factory)
    {            
        _factory = factory;
    }

    public IService Create(string name)
    {
        return _factory(name);
    }
}

Service registrations

builder.RegisterType<ServiceA>().As<ServiceA>();
builder.RegisterType<ServiceB>().As<ServiceB>();
builder.RegisterType<ServiceAFactory>().Keyed<IServiceFactory>(ServiceType.ServiceA);
builder.RegisterType<ServiceBFactory>().Keyed<IServiceFactory>(ServiceType.ServiceB);
builder.RegisterType<ComponentWithServiceDependency>().As<ComponentWithServiceDependency>(); 

Example usage

public class ComponentWithServiceDependency
{
    private readonly IService _service;

    public ComponentWithServiceDependency(IIndex<ServiceType, IServiceFactory> serviceFactories)
    {            
        // Resolve the ServiceB service implementation,
        // using the string "test" as its constructor dependency
        _service = serviceFactories[ServiceType.ServiceB].Create("test");
    }

    public string Test()
    {
        return _service.DoWork();
    }
}
like image 127
Matt Caton Avatar answered Oct 31 '22 17:10

Matt Caton


You can use Interface Segregation.

public interface IService
{
    void DoWork();
}

public interface IServiceA : IService
{
}

public interface IServiceB : IService
{
}

public class ServiceA : IServiceA
{
    private readonly string _name;

    public ServiceA(string name)
    {
        _name = name;
    }

    public void DoWork()
    {
        //ServiceA DoWork implementation
    }
}

public class ServiceB : IServiceB
{
    private readonly string _name;

    public ServiceB(string name)
    {
        _name = name;
    }

    public void DoWork()
    {
        //ServiceB DoWork implementation
    }
}

Then, you can inject delegate factories like so:

public class ClientA
{
    public ClientA(Func<string, IServiceA> serviceAFactory, Func<string, IServiceB> serviceBFactory)
    {
        this.serviceAFactory = serviceAFactory;
        this.serviceBFactory = serviceBFactory;
    }

    public CreateServices()
    {
        var runTimeName = "runTimeName";
        var serviceA = this.serviceAFactory(runTimeName);
        var serviceB = this.ServiceBFactory(runTimeName);
    }
}

Autofac will generate a delegate factory for each interface you register:

public class MyModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<ServiceA>()
            .As<IService>()
            .As<IServiceA>();

        builder.RegisterType<ServiceB>()
            .As<IService>()
            .As<IServiceB>();
    }
}
like image 39
l3a0 Avatar answered Oct 31 '22 18:10

l3a0