如何为.NET Core中的多区域配置Amazon S3客户端



我有ASP.NET Core应用程序。给定S3 URL,应用程序需要下载文件。这些S3 URL可能属于美国不同的AWS地区。

  public class Downloader
  {
      public void DownloadFile(string s3Url, string destFolder)
      {
          //download files
      }
  }

DownloadFile()方法被称为同时。每个URL传递到此方法可能属于不同的区域。

AWS文档显示了如何使用.NET Core为.NET配置AWS SDK。在我的情况下,AWS凭据存储在服务器上的配置文件中,并且可以在所有美国地区使用相同的凭据。所以appsettings.json看起来像

"AWS": {    
    "Profile": "default",
    "ProfilesLocation": "C:\aws\awsprofile"
  },

问题
由于URL可以属于不同的区域,因此我不能遵循文档代码。I 不能用DI框架注册IAmazonS3,然后将该实例注入Downloader中以从不同区域下载文件。因为IAmazonS3实例尝试到特定区域。

解决方案
因此,我创建了一个提供IAmazonS3给定区域名称的实例的工厂。

public interface IS3ClientFactory : IDisposable
{
    IAmazonS3 GetS3Client(RegionEndpoint region);
}
public class S3ClientFactory : IS3ClientFactory
{
    private bool _disposed = false;
    private IDictionary<string, IAmazonS3> _container = null;
    private S3ClientFactory()
    {
        _container = new Dictionary<string, IAmazonS3>();
    }
    public static IS3ClientFactory Configure(AWSOptions option, RegionEndpoint[] regions)
    {
        var factory = new S3ClientFactory();
        foreach (RegionEndpoint region in regions)
        {
            option.Region = region;
            factory._container.Add(region.SystemName, option.CreateServiceClient<IAmazonS3>());
        }
        return factory;
    }
    public IAmazonS3 GetS3Client(RegionEndpoint region)
    {            
        return _container[region.SystemName];
    }
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                if (_container != null && _container.Any())
                {
                    foreach (var s3Client in _container)
                    {
                        if (s3Client.Value != null)
                        {
                            s3Client.Value.Dispose();
                        }
                    }
                }
                _disposed = true;
            }
        }
    }
}

和di

的startup.cs注册工厂
services.AddSingleton<IS3ClientFactory>(S3ClientFactory.Configure(Configuration.GetAWSOptions(),
                new RegionEndpoint[]
                {
                    RegionEndpoint.USWest1,
                    RegionEndpoint.USWest2,
                    RegionEndpoint.USEast1,
                    RegionEndpoint.USEast2
                }));

Downloader类看起来像

public class Downloader : IDownloader
{        
    private readonly IS3ClientFactory _factory;
    public Downloader(IS3ClientFactory factory)
    {
       _factory = factory;        
    }
    public void DownloadFile(string s3Url, string destFolder)
    {
        var s3Uri = new AmazonS3Uri(s3Url);            
        var s3Client = _factory.GetS3Client(s3Uri.Region);
        // use s3Client to download file
    }
}

问题

  1. S3ClientFactory的配置方法中,我正在动态地将RegionEndpoint分配给AWSOptions,然后调用option.CreateServiceClient<IAmazonS3>()这是创建IAmazonS3特定区域特定实例的正确方法吗?该代码需要进行单位测试,因此我无法使用new AmazonS3Client(RegionEndpoint)

    foreach (RegionEndpoint region in regions)
    {
        option.Region = region;
        factory._container.Add(region.SystemName, option.CreateServiceClient<IAmazonS3>());
    } 
    
  2. 可以拥有IAmazonS3的单例实例?

您可以只注入服务界面。然后找到要使用linq的实例。

启动

foreach (string snsRegion in Configuration["SNSRegions"].Split(',', StringSplitOptions.RemoveEmptyEntries))
{
    services.AddAWSService<IAmazonSimpleNotificationService>(
        string.IsNullOrEmpty(snsRegion) ? null :
        new AWSOptions()
        {
            Region = RegionEndpoint.GetBySystemName(snsRegion)
        }
    );
}
services.AddSingleton<ISNSFactory, SNSFactory>();
services.Configure<SNSConfig>(Configuration);

snsconfig

public class SNSConfig
{
    public string SNSDefaultRegion { get; set; }
    public string SNSSMSRegion { get; set; }
}

appsettings.json

  "SNSRegions": "ap-south-1,us-west-2",
  "SNSDefaultRegion": "ap-south-1",
  "SNSSMSRegion": "us-west-2",

SNS工厂

public class SNSFactory : ISNSFactory
{
    private readonly SNSConfig _snsConfig;
    private readonly IEnumerable<IAmazonSimpleNotificationService> _snsServices;
    public SNSFactory(
        IOptions<SNSConfig> snsConfig,
        IEnumerable<IAmazonSimpleNotificationService> snsServices
        )
    {
        _snsConfig = snsConfig.Value;
        _snsServices = snsServices;
    }
    public IAmazonSimpleNotificationService ForDefault()
    {
        return GetSNS(_snsConfig.SNSDefaultRegion);
    }
    public IAmazonSimpleNotificationService ForSMS()
    {
        return GetSNS(_snsConfig.SNSSMSRegion);
    }
    private IAmazonSimpleNotificationService GetSNS(string region)
    {
        return GetSNS(RegionEndpoint.GetBySystemName(region));
    }
    private IAmazonSimpleNotificationService GetSNS(RegionEndpoint region)
    {
        IAmazonSimpleNotificationService service = _snsServices.FirstOrDefault(sns => sns.Config.RegionEndpoint == region);
        if (service == null)
        {
            throw new Exception($"No SNS service registered for region: {region}");
        }
        return service;
    }
}
public interface ISNSFactory
{
    IAmazonSimpleNotificationService ForDefault();
    IAmazonSimpleNotificationService ForSMS();
}

现在,您可以在自定义服务或控制器中获得所需区域的SNS服务

public class SmsSender : ISmsSender
{
    private readonly IAmazonSimpleNotificationService _sns;
    public SmsSender(ISNSFactory snsFactory)
    {
        _sns = snsFactory.ForSMS();
    }
    .......
 }
public class DeviceController : Controller
{
    private readonly IAmazonSimpleNotificationService _sns;
    public DeviceController(ISNSFactory snsFactory)
    {
        _sns = snsFactory.ForDefault();
    }
     .........
}

最新更新