同一存储的依赖注入多个接口(具有不同的connectionString)

Posted

技术标签:

【中文标题】同一存储的依赖注入多个接口(具有不同的connectionString)【英文标题】:dependency injection multiple interface for the same storage (with different connectionString) 【发布时间】:2021-09-25 13:51:13 【问题描述】:

我想在 Startup.cs 中添加两个或更多(取决于我要添加到我的应用程序的 azure 存储容器的数量)服务

我的 appsettings.json:

"AzureBlobStorageConfiguration": 
    "Storages": 
      "Storage1": 
        "StorageName": "Storage1",
        "ConnString": "connString",
        "AzureBlobContainerName": "containerName"
      ,
      "Storage2": 
        "StorageName": "Storage2",
        "ConnString": "connString",
        "AzureBlobContainerName": "containerName"
      ,
      "Storage3": 
        "StorageName": "Storage3",
        "ConnString": "connString",
        "AzureBlobContainerName": "containerName"
      
    

接下来在 Startup.cs 中使用方法添加服务:

 public static IServiceCollection AddAzureStorage1(this IServiceCollection services, IConfiguration configuration)
        
            var options = new ABlobStorageConfigurationOptionsDTO();
            configuration.GetSection("AzureBlobStorageConfiguration").GetSection("Storages").GetSection("Storage1").Bind(options);

            services.AddTransient<IAzureBlobStorage1, AzureBlobStorage1>(isp =>
            
                var client = new BlobServiceClient(options.ConnString);
                var container = client.GetBlobContainerClient(options.AzureBlobContainerName);
                var containerName = options.AzureBlobContainerName;
                var storageName = options.StorageName;

                return new AzureBlobStorage1(container, containerName, storageName);
            
                );
            return services;
        

我的 IAzureBlobStorage1 看起来像:

public interface IAzureBlobStorage1
    
        string AzureBlobContainerName  get; 
        string StorageName  get; 

        public Task<Stream> DownloadStreamAsyns(string fileName);

        public Task Upload(string fileId, Stream stream);

    

和 AzureBlobStorage1:

    public class AzureBlobStorage1 : IAzureBlobStorage1
    
        private BlobContainerClient _client;
        private string _containerName;
        private string _storageName;
        public string StorageName => _storageName;
        public string AzureBlobContainerName => _containerName;

        public AzureBlobStorage1(BlobContainerClient client, string containerName, string storageName)
        
            _client = client;
            _containerName = containerName;
            _storageName = storageName;
        


        public async Task<Stream> DownloadStreamAsyns(string fileName)
        
            return await _client.GetBlobClient(fileName).OpenReadAsync();
        

        public async Task Upload(string fileId, Stream stream)
        
            await _client.GetBlobClient(fileId).UploadAsync(stream);
        
    

之后我可以在我的构造函数控制器类中注入接口:

        public Controller(IAzureBlobStorage1 azureStorage)
        
            _azureStorage1 = azureStorage;

        

但如果我想添加许多存储(我在 appsetings.json 中有 3 个),我必须:

    创建接口 IAzureBlobStorage2(看起来像 IAzureBlobStorage1 - 只是名称更改)

    创建类 AzureBlobStorage2(看起来与 AzureBlobStorage1 相同 - 仅更改名称)

    更改类名的复制粘贴方法

public static IServiceCollection AddAzureStorage2(this IServiceCollection services, IConfiguration configuration)
        
            var options = new ABlobStorageConfigurationOptionsDTO();
            configuration.GetSection("AzureBlobStorageConfiguration").GetSection("Storages").GetSection("Storage2").Bind(options);

            services.AddTransient<IAzureBlobStorage2, AzureBlobStorage2>(isp =>
            
                var client = new BlobServiceClient(options.ConnString);
                var container = client.GetBlobContainerClient(options.AzureBlobContainerName);
                var containerName = options.AzureBlobContainerName;
                var storageName = options.StorageName;

                return new AzureBlobStorage2(container, containerName, storageName);
            
                );
            return services;
        

现在我可以在控制器中通过

  public Controller(IAzureBlobStorage2 azureStorage)
        
            _azureStorage2 = azureStorage;

        

如果我想添加我的第三个存储,我需要第三次复制粘贴我的代码。

对我来说,这个解决方案看起来很糟糕,我正在考虑如何解决它并让我的代码干净。

【问题讨论】:

创建一个包含 blob 存储对象列表的类,在设置中使用可用的内容对其进行配置,然后注入该类。用户将能够从单个持有者中提取单独的存储。 【参考方案1】:

不确定这是否是最佳实践,但您可以设计一个命名服务提供商,也许吧?要么,或者你可以只是一个通用参数来区分它们,但是这个通用参数除了作为一种区分方式之外没有多大意义..

无论如何,这是一个使用某种命名提供程序的非常基本的实现?:

public interface INamedService 
    string Identifier  get; 


public interface IAzureBlobStorage : INamedService

    string AzureBlobContainerName  get; 
    string StorageName  get; 
    Task<Stream> DownloadStreamAsyns(string fileName);
    Task Upload(string fileId, Stream stream);


public class NamedServiceProvider<T> 
    where T : INamedService

    readonly IReadOnlyDictionary<string, T> Instances;

    public NamedServiceProvider(
        IEnumerable<T> instances) 
    
        Instances = instances?.ToDictionary(x => x.Identifier) ??
            throw new ArgumentNullException(nameof(instances));
    

    public bool TryGetInstance(string identifier, out T instance) 
        return Instances.TryGetValue(identifier, out instance);
    


public class AzureBlobStorage : IAzureBlobStorage

    public string Identifier  get; 
    private BlobContainerClient _client;
    private string _containerName;
    private string _storageName;
    public string StorageName => _storageName;
    public string AzureBlobContainerName => _containerName;

    public AzureBlobStorage(string identifier, BlobContainerClient client, string containerName, string storageName)
    
        Identifier = identifier;
        _client = client;
        _containerName = containerName;
        _storageName = storageName;
    


    public async Task<Stream> DownloadStreamAsyns(string fileName)
    
        return await _client.GetBlobClient(fileName).OpenReadAsync();
    

    public async Task Upload(string fileId, Stream stream)
    
        await _client.GetBlobClient(fileId).UploadAsync(stream);
    

然后是静态扩展方法:

public static IServiceCollection AddAzureStorage(
    this IServiceCollection services, 
    IConfiguration configuration,
    string identifier)

    var options = new ABlobStorageConfigurationOptionsDTO();
    configuration
        .GetSection("AzureBlobStorageConfiguration")
        .GetSection("Storages")
        .GetSection(identifier)
        .Bind(options);

    return services
        .TryAddTransient<NamedServiceProvider<IAzureBlobStorage>>()
        .AddTransient<IAzureBlobStorage, AzureBlobStorage>(isp =>
    
        var client = new BlobServiceClient(options.ConnString);
        var container = client.GetBlobContainerClient(options.AzureBlobContainerName);
        var containerName = options.AzureBlobContainerName;
        var storageName = options.StorageName;

        return new AzureBlobStorage(identifier, container, containerName, storageName);
    );

然后你可以这样调用它:

public Controller(NamedServiceProvider<IAzureBlobStorage> azureStorage)

    _ = azureStorage ?? throw new ArgumentNullException(nameof(azureStorage));
    _azureStorage2 = azureStorage.TryGetInstance("Storage2", out var instance) ? instance : throw new Exception("Something about the identifier not being found??");

我在智能感知环境之外对此进行了编码,如果有任何较小的拼写错误或错误,敬请见谅。可能有更好的方法来做到这一点,但这似乎至少有点ok-ish?哦,我只改变了我必须做的事情,以使其能够正常工作。我不想触及任何其他逻辑..

【讨论】:

以上是关于同一存储的依赖注入多个接口(具有不同的connectionString)的主要内容,如果未能解决你的问题,请参考以下文章

具有多个接口作为构造函数中的参数的依赖注入

.Net Core 中同一接口多个实现依赖注入的问题

同一方法的多个实现,可能与Go和Interfaces具有不同的依赖性

我在项目中运用 IOC(依赖注入)--实战篇

使用具有依赖注入的策略和工厂模式

从同一个表mysql存储过程中选择具有不同条件的多个计数