6

I have a BlobStorageRepository class which is being used to interact with Azure blob storage. It implements an interface IBlobStorageRepository.

I have the need to use two instances of BlobStorageRepository but they are setup with different credentials:

In my Startup.cs:

// Normal storage
var storageCredentials = new StorageCredentials(_appSettings.BlobStorageAccountName, _appSettings.BlobStorageAccountKey);
var cloudBlobStorageAccount = new CloudStorageAccount(storageCredentials, true);
var cloudBlobClient = cloudBlobStorageAccount.CreateCloudBlobClient();
var blobRepository = new BlobStorageRepository(cloudBlobClient);

// Quarantine storage
var quarantineStorageCredentials = new StorageCredentials(_appSettings.QuarantineBlobStorageAccountName, _appSettings.QuarantineBlobStorageAccountKey);
var quarantineCloudBlobStorageAccount = new CloudStorageAccount(quarantineStorageCredentials, true);
var quarantineCloudBlobClient = quarantineCloudBlobStorageAccount.CreateCloudBlobClient();
var quarantineBlobRepository = new BlobStorageRepository(quarantineCloudBlobClient);

services.AddSingleton<IBlobStorageRepository>(blobRepository);
services.AddSingleton<IBlobStorageRepository>(quarantineBlobRepository);

How can I configure the above so that I can resolve the dependencies in my class and get the two different instances i.e:

private readonly IBlobStorageRepository _blobStorageRepository;
private readonly IBlobStorageRepository _quarantineBlobStorageRepository;

public DocumentService(IBlobStorageRepository blobStorageRepository, IBlobStorageRepository quarantineBlobStorageRepository)
{
    _blobStorageRepository = blobStorageRepository;
    _quarantineBlobStorageRepository = quarantineBlobStorageRepository;
}

Edit: My question is not a duplicate of How to register multiple implementations of the same interface in Asp.Net Core? because I want to pass the instantiated class when adding the service i.e. services.AddSingleton<IBlobStorageRepository>(blobRepository) and you cannot pass the instantiated class to serviceProvider.GetService<BlobStorageRepository>().

Chris
  • 3,113
  • 5
  • 24
  • 46
  • 1
    Possible duplicate of [How to register multiple implementations of the same interface in Asp.Net Core?](https://stackoverflow.com/questions/39174989/how-to-register-multiple-implementations-of-the-same-interface-in-asp-net-core) – Pavel Anikhouski Jul 10 '19 at 13:41
  • @PavelAnikhouski this is not a duplicate. The use case is different. – Chris Jul 10 '19 at 13:45
  • @chris Idea is the same. You should register all implementations as self + factory to get instance by name and then inject factory not interfaces: `_blobStorageRepository = repoFactory.Get(RepoType.Blob);` – mtkachenko Jul 10 '19 at 13:52
  • @mtkachenko how can I get instance by name? A code example would great if possible. – Chris Jul 10 '19 at 13:54

3 Answers3

8

Out of the box, all you can do is inject all instances of the interface using:

public MyClass(List<IBlobStorageRepository> repos)

Then, you'll need to decide for yourself how to pull the right instance out of the list for each ivar you want to set.

It's generally better in this type of scenario to create a factory class and inject that instead. For example, you could have a BlobStorageRepositoryFactory and provide a way to return a repository instance for the particular scenario you require. That could be a method, property, etc. It's up to you. The point is that the factory coordinates the logic for both managing the repositories and providing an API to retrieve the right one.

Alternatively, you can supplement Microsoft.Extensions.DependencyInjection with a more robust DI container. A list of supported containers can be found at: https://github.com/aspnet/Extensions/tree/master/src/DependencyInjection.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
5

Unfortunately I can't find "named instances" functionality in embeded ASP.NET Core DI container. In Autofac you can use Named and Keyed Services.

In your case you can do something like this:

public enum RepoTypes { Blob, Quarantine }

...

public interface IRepoResolver
{
    //resolve using method
    IBlobStorageRepository GetRepo(RepoTypes rt);

    //resolve usnig indexer
    IBlobStorageRepository this[RepoTypes rt] { get; }
}

...

public sealed RepoResolver : IRepoResolver
{
    private readonly Dictionary<RepoTypes, IBlobStorageRepository> _repos;

    public RepoResolver()
    {
        _repos[RepoTypes.Blob] = new BlobStorageRepository(...);
        _repos[RepoTypes.Quarantine] = new BlobStorageRepository(...);
    }

    public IBlobStorageRepository GetRepo(RepoTypes rt) => _repos[rt];

    public IBlobStorageRepository this[RepoTypes rt] { get => _repos[rt]; }
}

...

services.AddSingleton<IRepoResolver, RepoResolver>();

...

public class Some
{
    public Some(IRepoResolver rr)
    {
        var blob1 = rr.GetRepo(RepoTypes.Blob);
        var blob2 = rr[RepoTypes.Blob];
    }
}
mtkachenko
  • 5,389
  • 9
  • 38
  • 68
2

For future reference - the way I solved this using a combination of mtkachenko and Chris Pratt's answers:

Created a BlobStorageRepositoryFactory:

public enum BlobStorageRepositoryType
{
    Core,
    Quarantine
}

public interface IBlobStorageRepositoryFactory
{
    void Register(BlobStorageRepositoryType type, IBlobStorageRepository blobRepository);
    IBlobStorageRepository Resolve(BlobStorageRepositoryType type);
}

public class BlobStorageRepositoryFactory : IBlobStorageRepositoryFactory
{
    private readonly ConcurrentDictionary<BlobStorageRepositoryType, IBlobStorageRepository> _blobRepositories = new ConcurrentDictionary<BlobStorageRepositoryType, IBlobStorageRepository>();

    public void Register(BlobStorageRepositoryType type, IBlobStorageRepository blobRepository)
    {
        _blobRepositories[type] = blobRepository;
    }

    public IBlobStorageRepository Resolve(BlobStorageRepositoryType type)
    {
        return _blobRepositories[type];
    }
}

Then in my Startup.cs:

var blobStorageRepositoryFactory = new BlobStorageRepositoryFactory();
blobStorageRepositoryFactory.Register(BlobStorageRepositoryType.Core, GetBlobStorageRepository(_appSettings.BlobStorageAccountName, _appSettings.BlobStorageAccountKey));
blobStorageRepositoryFactory.Register(BlobStorageRepositoryType.Quarantine, GetBlobStorageRepository(_appSettings.QuarantineBlobStorageAccountName, _appSettings.QuarantineBlobStorageAccountKey));
services.AddSingleton<IBlobStorageRepositoryFactory>(blobStorageRepositoryFactory);

private static BlobStorageRepository GetBlobStorageRepository(string accountName, string accountKey)
{
    var storageCredentials = new StorageCredentials(accountName, accountKey);
    var cloudBlobStorageAccount = new CloudStorageAccount(storageCredentials, true);
    var cloudBlobClient = cloudBlobStorageAccount.CreateCloudBlobClient();
    var blobRepository = new BlobStorageRepository(cloudBlobClient);
    return blobRepository;
}

And finally in my service:

private readonly IBlobStorageRepository _blobStorageRepository;
private readonly IBlobStorageRepository _quarantineBlobStorageRepository;

public DocumentService(IBlobStorageRepositoryFactory blobStorageRepositoryFactory)
{
    _blobStorageRepository = blobStorageRepositoryFactory.Resolve(BlobStorageRepositoryType.Core);
    _quarantineBlobStorageRepository = blobStorageRepositoryFactory.Resolve(BlobStorageRepositoryType.Quarantine);
}
Chris
  • 3,113
  • 5
  • 24
  • 46
  • 1
    Having `void Register(...)` method is a bad idea. You use `Dictionary` inside `BlobStorageRepositoryFactory` and `BlobStorageRepositoryFactory` is a singleton. So it's no threadsafe. In your case it's possible to write and read `_blobRepositories` concurrently from different threads but `Dictionary` is not thread-safe. It works OK in `my example` because I initialize data inside constructor so you can't modify it during application lifetime. You still can use `ConcurrentDictionary` but why? In my example it's thread-safe and still works OK with `Dictionary`. – mtkachenko Jul 11 '19 at 08:45
  • @mtkachenko thank you for this. I will use your example instead. – Chris Jul 11 '19 at 19:58