Description
Describe the feature
Expose the ability to register a service-client with service-specific config that reuses the default AWSOptions
.
Use Case
I'd like to register an aws service with client-specific config overrides that uses the default AWSOptions
. Without the ability to do this, I would have to duplicate option declarations (e.g. Credentials
or ServiceURL
) for each service-specific config I need to setup.
This is particularly important in scenarios where registrations can be modified outside of startup (e.g. WebApplicationFactory).
Ultimately I want my DI registrations to be the source of truth for how the AWS SDK behaves, and I want these registrations to be clean and DRY.
Here's a contrived example. An explicit AWSOptions
instantiation is used for emphasis.
using Amazon.Extensions.NETCore.Setup;
using Amazon.Runtime;
using Amazon.S3;
using Amazon.SQS;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddDefaultAWSOptions(new AWSOptions
{
Credentials = new BasicAWSCredentials("accessKey", "accessSecret"),
DefaultClientConfig =
{
ServiceURL = "localhost:6379",
},
})
.AddAWSService<IAmazonSQS>() // sqs will use the default "global" options added above
.AddSingleton<IAmazonS3>(sp =>
{
// The only way to create an AWSOptions object with an AmazonS3Config DefaultClientConfig prop is via this extension method.
var awsOptions = sp.GetRequiredService<IConfiguration>().GetAWSOptions<AmazonSQSConfig>();
// We have to declare these options again.
awsOptions.Credentials = new BasicAWSCredentials("accessKey", "accessSecret");
awsOptions.DefaultClientConfig.ServiceURL = "localhost:6379";
var s3Config = (AmazonS3Config)awsOptions.DefaultClientConfig;
s3Config.ForcePathStyle = true;
s3Config.MaxErrorRetry = 10;
// Set any other s3-specific config here
return awsOptions.CreateServiceClient<IAmazonS3>();
});
var app = builder.Build();
app.Run();
Proposed Solution
There are a number of approaches that could be taken. Here are some ideas in order of my personal preference (although I'd do 1 & 2 together):
1.
Expose an extension method that allows for service-specific config customization.
builder.Services
.AddDefaultAWSOptions(sp => sp.GetRequiredService<IConfiguration>().GetAWSOptions())
.AddAWSService<IAmazonSQS>()
.AddAWSService<IAmazonS3, AmazonSQSConfig>((IServiceProvider sp, AmazonS3Config s3Config) =>
{
s3Config.ForcePathStyle = true;
s3Config.MaxErrorRetry = 10;
return s3Config;
});
2.
Expose an extension method that provides a (new) AWSOptions
object created using the default AWSOptions if one is registered.
builder.Services
.AddDefaultAWSOptions(sp => sp.GetRequiredService<IConfiguration>().GetAWSOptions())
.AddAWSService<IAmazonSQS>()
.AddAWSService<IAmazonS3, AmazonS3Config>((IServiceProvider sp, AWSOptions awsOptions) =>
{
var sqsOptions = (AmazonS3Config)awsOptions.DefaultClientConfig;
sqsOptions.ForcePathStyle = true;
sqsOptions.MaxErrorRetry = 10;
return awsOptions;
})
3.
Expose a method/constructor that creates an AWSOptions
object from another. Note that this example also updates AWSOptions
to take a generic for ease of use, but that's not strictly necessary as long as there's some means of creating one outside of the IConfiguration extension method. Note that this also exposes a new AddAWSService
extension method that takes a Func<IServiceProvider, AWSOptions>
arg.
builder.Services
.AddDefaultAWSOptions(sp => sp.GetRequiredService<IConfiguration>().GetAWSOptions())
.AddAWSService<IAmazonSQS>()
.AddAWSService<IAmazonS3>(sp =>
{
var awsOptions = sp.GetRequiredService<AWSOptions>();
var sqsOptions = new AWSOptions<AmazonS3Config>(awsOptions);
sqsOptions.DefaultClientConfig.ForcePathStyle = true;
sqsOptions.DefaultClientConfig.MaxErrorRetry = 10;
return sqsOptions;
})
Other Information
The most reasonable approach I could muster is to create an ugly ApplyPropsFrom
extension method.
using Amazon.Extensions.NETCore.Setup;
using Amazon.S3;
using Amazon.SQS;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddDefaultAWSOptions(sp => sp.GetRequiredService<IConfiguration>().GetAWSOptions())
.AddAWSService<IAmazonSQS>()
.AddSingleton<IAmazonS3>(sp =>
{
var globalAwsOptions = sp.GetRequiredService<AWSOptions>();
var config = sp.GetRequiredService<IConfiguration>();
var s3Options = config.GetAWSOptions<AmazonS3Config>();
s3Options.ApplyPropsFrom(globalAwsOptions);
var s3Config = (AmazonS3Config)s3Options.DefaultClientConfig;
s3Config.ForcePathStyle = true;
s3Config.MaxErrorRetry = 10;
// Set any other s3-specific config here
return s3Options.CreateServiceClient<IAmazonS3>();
});
var app = builder.Build();
app.Run();
using Amazon.Extensions.NETCore.Setup;
using Amazon.Runtime;
namespace MyApp;
public static class AWSOptionsExtensions
{
public static void ApplyPropsFrom(this AWSOptions options, AWSOptions? other)
{
if (other == null)
{
return;
}
// Hope and pray that AWSOptions props never change...
options.Credentials = other.Credentials;
options.Region = other.Region;
options.Logging = other.Logging;
options.Profile = other.Profile;
options.ExternalId = other.ExternalId;
options.ProfilesLocation = other.ProfilesLocation;
options.SessionName = other.SessionName;
options.DefaultConfigurationMode = other.DefaultConfigurationMode;
options.SessionRoleArn = other.SessionRoleArn;
options.DefaultClientConfig?.ApplyPropsFrom(other.DefaultClientConfig);
}
public static void ApplyPropsFrom(this ClientConfig config, ClientConfig? other)
{
if (other == null)
{
return;
}
// Hope and pray that ClientConfig props never change...
config.ServiceId = other.ServiceId;
config.DefaultConfigurationMode = other.DefaultConfigurationMode;
config.RegionEndpoint = other.RegionEndpoint;
config.ThrottleRetries = other.ThrottleRetries;
config.UseHttp = other.UseHttp;
config.UseAlternateUserAgentHeader = other.UseAlternateUserAgentHeader;
config.ServiceURL = other.ServiceURL;
config.SignatureVersion = other.SignatureVersion;
config.ClientAppId = other.ClientAppId;
config.SignatureMethod = other.SignatureMethod;
config.LogResponse = other.LogResponse;
config.BufferSize = other.BufferSize;
config.ProgressUpdateInterval = other.ProgressUpdateInterval;
config.ResignRetries = other.ResignRetries;
config.ProxyCredentials = other.ProxyCredentials;
config.LogMetrics = other.LogMetrics;
config.DisableLogging = other.DisableLogging;
config.AllowAutoRedirect = other.AllowAutoRedirect;
config.UseDualstackEndpoint = other.UseDualstackEndpoint;
config.UseFIPSEndpoint = other.UseFIPSEndpoint;
config.DisableRequestCompression = other.DisableRequestCompression;
config.RequestMinCompressionSizeBytes = other.RequestMinCompressionSizeBytes;
config.DisableHostPrefixInjection = other.DisableHostPrefixInjection;
config.EndpointDiscoveryEnabled = other.EndpointDiscoveryEnabled;
config.IgnoreConfiguredEndpointUrls = other.IgnoreConfiguredEndpointUrls;
config.EndpointDiscoveryCacheLimit = other.EndpointDiscoveryCacheLimit;
config.RetryMode = other.RetryMode;
config.TelemetryProvider = other.TelemetryProvider;
config.AccountIdEndpointMode = other.AccountIdEndpointMode;
config.RequestChecksumCalculation = other.RequestChecksumCalculation;
config.ResponseChecksumValidation = other.ResponseChecksumValidation;
config.ProxyHost = other.ProxyHost;
config.ProxyPort = other.ProxyPort;
config.Profile = other.Profile;
config.AWSTokenProvider = other.AWSTokenProvider;
config.AuthenticationRegion = other.AuthenticationRegion;
config.AuthenticationServiceName = other.AuthenticationServiceName;
config.MaxErrorRetry = other.MaxErrorRetry;
config.FastFailRequests = other.FastFailRequests;
config.CacheHttpClient = other.CacheHttpClient;
config.HttpClientCacheSize = other.HttpClientCacheSize;
config.EndpointProvider = other.EndpointProvider;
config.MaxConnectionsPerServer = other.MaxConnectionsPerServer;
config.HttpClientFactory = other.HttpClientFactory;
if (other.Timeout.HasValue)
{
config.Timeout = other.Timeout.Value;
}
}
}
Acknowledgements
- I may be able to implement this feature request
- This feature might incur a breaking change
AWS .NET SDK and/or Package version used
<PackageVersion Include="AWSSDK.Core" Version="3.7.402.19" />
<PackageVersion Include="AWSSDK.S3" Version="3.7.201.1" />
<PackageVersion Include="AWSSDK.SQS" Version="3.7.400.102" />
<PackageVersion Include="AWSSDK.SSO" Version="3.7.400.113" />
<PackageVersion Include="AWSSDK.SSOOIDC" Version="3.7.400.114" />
<PackageVersion Include="AWSSDK.SecurityToken" Version="3.7.202.11" />
<PackageVersion Include="AWSSDK.SimpleEmail" Version="3.7.200.29" />
Targeted .NET Platform
.NET 8
Operating System and version
macOS