Search code examples
simple-injector

Registering multiple AmazonS3Clients in SimpleInjector and configuring Adapter


The issue is as follows:

I have to access 2 different S3 buckets and the way I have to create AmazonS3clients is as follows:

var client = new AmazonS3Client(accesKey,secretKey, bucketRegion);

Since I have 2 clients I ended up following the advice in the book "Dependency Injection Principles, Practices, and Patterns" and in particular the advice in Chapter 6 whereby instead of using an Abstract Factory, I end up using an Adapter.

Here is the code of interest:

static async Task Main(string[] args)
{
    var s3Clients = new Dictionary<S3ClientType, IAmAnS3ClientQueryor>
    {
        {
            S3ClientType.Discriminator1,
            new S3Queryor(apiKey1, secretKey1, bucketRegion))
        },
        {
            S3ClientType.Discriminator2,
            new S3Queryor(new AmazonS3Client(apiKey2, secretKey2, bucketRegion))
        },
    };
    IGetFileNames fileNamesGetter = new S3FileNamesGetter(s3Clients);
    var uidMappingFileNames = fileNamesGetter.GetAsync(S3ClientType.Discriminator2, bucketName);
}

public interface IGetFileNames
{
    IAsyncEnumerable<string> GetAsync(S3ClientType discriminator, string bucketName);
}

public class S3FileNamesGetter: IGetFileNames
{
    private readonly IDictionary<S3ClientType, IAmAnS3ClientQueryor> s3Clients;

    public S3FileNamesGetter(IDictionary<S3ClientType, IAmAnS3ClientQueryor> s3Clients)
    {
        this.s3Clients = s3Clients;
    }

    public IAsyncEnumerable<string> GetAsync(S3ClientType type, string bucketName)
    {
        return s3Clients[type].GetFileNames(bucketName, token);
    }
}

public interface IAmAnS3ClientQueryor
{
    IAsyncEnumerable<string> GetFileNames(string bucketName);
}

public class S3Queryor : IAmAnS3ClientQueryor
{
    private readonly IAmazonS3 s3Client;

    public S3Queryor(IAmazonS3 s3Client)
    {
        this.s3Client = s3Client;
    }

    public async IAsyncEnumerable<string> GetFileNames(string bucketName)
    {
        // Returns filenames from S3
    }
}

In my attempt to use SimpleInjector, I have:

static async Task Main(string[] args)
{
    var container = new Container();

    Bootstrap.Start(container, bucketRegion);

    var fileNamesGetter = container.GetInstance<IGetFileNames>();
    var uidMappingFileNames =
    fileNamesGetter.GetAsync(S3ClientType.Discriminator2, bucketName);
}

internal class Bootstrap
{
    public static void Start(Container container, RegionEndpoint bucketRegion)
    {
        container.Register<S3FileNamesGetter>();
        container.Options.EnableAutoVerification = false;

        var client1 = new AmazonS3Client( apiKey1, secretKey1, bucketRegion);
        var client2 = new AmazonS3Client( apiKey2, secretKey2, bucketRegion);

        var s3ClientsQueryors = new Dictionary<S3ClientType, IAmAnS3ClientQueryor>
        {
            {
                S3ClientType.Discriminator1, new S3Queryor(client1)
            },
            {
                S3ClientType.Discriminator2, new S3Queryor(client2)
            }
        };

        container.Collection.Register<IAmazonS3>(client1, client2);

        container.RegisterInstance<IDictionary<S3ClientType, IAmAnS3ClientQueryor>>(s3ClientsQueryors);

        container.RegisterInstance<IGetFileNames>(new S3FileNamesGetter(s3ClientsQueryors));
        container.Register<IAmAnS3ClientQueryor, S3Queryor>();
        container.Verify();
    }
}

but when I run this code, I quite rightly get:

Unhandled exception. System.InvalidOperationException: The configuration is invalid. Creating the instance for type IAmAnS3ClientQueryor failed. The constructor of type S3Queryor contains the parameter with name 's3Client' and type IAmazonS3, bu t IAmazonS3 is not registered. For IAmazonS3 to be resolved, it must be registered in the container. There is, however, a registration for a collection of IAmazonS3 instances; Did you mean to depend on IEnumerable<IAmazonS3> instead? If you mean t to depend on IAmazonS3, you should use one of the Container.Register overloads instead of using Container.Collection.Register. Please see https://simpleinjector.org/collections for more information about registering and resolving collections. --->

The message is clear. However, the problem in the 1st instance is the fact that I have 2 S3 clients which I want to be eventually injected in S3Queryor.

So the question is how to register my dependencies in Simple Injector to use the Adapter pattern.

=== EDIT after @Steven 1st comment.

I have created a repo of the code above here https://github.com/DavidSSL/SI-Trial


Solution

  • Here is the way to do it thanks to @Steven:

    internal static class Bootstrap
    {
        public static void Start(Container container, RegionEndpoint bucketRegion)
        {
            container.RegisterInstance<IGetFileNames>(new S3FileNamesGetter(
                new Dictionary<S3ClientType, IAmAnS3ClientQueryor>
                {
                    {
                        S3ClientType.Discriminator1,
                        new S3Queryor(new AmazonS3Client("apiKey1", "secretKey1", bucketRegion))
                    },
                    {
                        S3ClientType.Discriminator2,
                        new S3Queryor(new AmazonS3Client("apiKey2", "secretKey2", bucketRegion))
                    },
                }));
    
            container.Options.EnableAutoVerification = false;
    
            container.Verify();
        }
    }