Skip to content

Add possibility to add AWS services to DI with a key #3570

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,17 @@

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' != 'net8.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.0.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
</ItemGroup>


<ItemGroup>
<ProjectReference Include="../../../sdk/src/Core/AWSSDK.Core.NetStandard.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,5 +162,78 @@ public static IServiceCollection TryAddAWSService<T>(this IServiceCollection col
collection.TryAdd(descriptor);
return collection;
}

#if NET8_0_OR_GREATER

/// <summary>
/// Adds the Amazon service client to the dependency injection framework with a key. The Amazon service client is not
/// created until it is requested. If the ServiceLifetime property is set to Singleton, the default, then the same
/// instance will be reused for the lifetime of the process and the object should not be disposed.
/// </summary>
/// <typeparam name="T">The AWS service interface, like IAmazonS3</typeparam>
/// <param name="collection"></param>
/// <param name="serviceKey">The key with which the service will be added in the dependency injection framework.</param>
/// <param name="lifetime">The lifetime of the service client created. The default is Singleton.</param>
/// <returns>Returns back the IServiceCollection to continue the fluent system of IServiceCollection.</returns>
public static IServiceCollection AddKeyedAWSService<T>(this IServiceCollection collection, object serviceKey, ServiceLifetime lifetime = ServiceLifetime.Singleton) where T : IAmazonService
{
return AddKeyedAWSService<T>(collection, serviceKey, null, lifetime);
}

/// <summary>
/// Adds the Amazon service client to the dependency injection framework with a key. The Amazon service client is not
/// created until it is requested. If the ServiceLifetime property is set to Singleton, the default, then the same
/// instance will be reused for the lifetime of the process and the object should not be disposed.
/// </summary>
/// <typeparam name="T">The AWS service interface, like IAmazonS3</typeparam>
/// <param name="collection"></param>
/// <param name="serviceKey">The key with which the service will be added in the dependency injection framework.</param>
/// <param name="options">The AWS options used to create the service client overriding the default AWS options added using AddDefaultAWSOptions.</param>
/// <param name="lifetime">The lifetime of the service client created. The default is Singleton.</param>
/// <returns>Returns back the IServiceCollection to continue the fluent system of IServiceCollection.</returns>
public static IServiceCollection AddKeyedAWSService<T>(this IServiceCollection collection, object serviceKey, AWSOptions options, ServiceLifetime lifetime = ServiceLifetime.Singleton) where T : IAmazonService
{
object Factory(IServiceProvider sp, object key) => new ClientFactory<T>(options).CreateServiceClient(sp);

var descriptor = new ServiceDescriptor(typeof(T), serviceKey, Factory, lifetime);
collection.Add(descriptor);
return collection;
}

/// <summary>
/// Adds the Amazon service client to the dependency injection framework with a key if the service type hasn't already been registered with the same key.
/// The Amazon service client is not created until it is requested. If the ServiceLifetime property is set to Singleton, the default, then the same
/// instance will be reused for the lifetime of the process and the object should not be disposed.
/// </summary>
/// <typeparam name="T">The AWS service interface, like IAmazonS3</typeparam>
/// <param name="collection"></param>
/// <param name="serviceKey">The key with which the service will be added in the dependency injection framework.</param>
/// <param name="lifetime">The lifetime of the service client created. The default is Singleton.</param>
/// <returns>Returns back the IServiceCollection to continue the fluent system of IServiceCollection.</returns>
public static IServiceCollection TryAddKeyedAWSService<T>(this IServiceCollection collection, object serviceKey, ServiceLifetime lifetime = ServiceLifetime.Singleton) where T : IAmazonService
{
return TryAddKeyedAWSService<T>(collection, serviceKey, null, lifetime);
}

/// <summary>
/// Adds the Amazon service client to the dependency injection framework with a key if the service type hasn't already been registered with the same key.
/// The Amazon service client is not created until it is requested. If the ServiceLifetime property is set to Singleton, the default, then the same
/// instance will be reused for the lifetime of the process and the object should not be disposed.
/// </summary>
/// <typeparam name="T">The AWS service interface, like IAmazonS3</typeparam>
/// <param name="collection"></param>
/// <param name="serviceKey">The key with which the service will be added in the dependency injection framework.</param>
/// <param name="options">The AWS options used to create the service client overriding the default AWS options added using AddDefaultAWSOptions.</param>
/// <param name="lifetime">The lifetime of the service client created. The default is Singleton.</param>
/// <returns>Returns back the IServiceCollection to continue the fluent system of IServiceCollection.</returns>
public static IServiceCollection TryAddKeyedAWSService<T>(this IServiceCollection collection, object serviceKey, AWSOptions options, ServiceLifetime lifetime = ServiceLifetime.Singleton) where T : IAmazonService
{
object Factory(IServiceProvider sp, object key) => new ClientFactory<T>(options).CreateServiceClient(sp);

var descriptor = new ServiceDescriptor(typeof(T), serviceKey, Factory, lifetime);
collection.TryAdd(descriptor);
return collection;
}
#endif
}
}
151 changes: 151 additions & 0 deletions extensions/test/NETCore.SetupTests/DependencyInjectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,157 @@ public void InjectS3ClientWithFactoryBuiltConfig()
Assert.NotNull(controller.S3Client);
Assert.Equal(expectRegion, controller.S3Client.Config.RegionEndpoint);
}

#if NET8_0_OR_GREATER

[Fact]
public void InjectKeyedS3ClientWithDefaultConfig()
{
var builder = new ConfigurationBuilder();
builder.AddJsonFile("./TestFiles/GetClientConfigSettingsTest.json");

IConfiguration config = builder.Build();

ServiceCollection services = new ServiceCollection();
services.AddDefaultAWSOptions(config.GetAWSOptions());
services.AddKeyedAWSService<IAmazonS3>(TestControllerKeyed.Key);

var serviceProvider = services.BuildServiceProvider();

var controller = ActivatorUtilities.CreateInstance<TestControllerKeyed>(serviceProvider);
Assert.NotNull(controller.S3Client);
Assert.Equal(RegionEndpoint.USWest2, controller.S3Client.Config.RegionEndpoint);
}

[Fact]
public void InjectKeyedS3ClientWithoutDefaultConfig()
{
ServiceCollection services = new ServiceCollection();
services.AddKeyedAWSService<IAmazonS3>(TestControllerKeyed.Key, new AWSOptions {Region = RegionEndpoint.USEast1 });

var serviceProvider = services.BuildServiceProvider();

var controller = ActivatorUtilities.CreateInstance<TestControllerKeyed>(serviceProvider);
Assert.NotNull(controller.S3Client);
Assert.Equal(RegionEndpoint.USEast1, controller.S3Client.Config.RegionEndpoint);
}

[Fact]
public void InjectKeyedS3ClientWithOverridingConfig()
{
var builder = new ConfigurationBuilder();
builder.AddJsonFile("./TestFiles/GetClientConfigSettingsTest.json");

IConfiguration config = builder.Build();

ServiceCollection services = new ServiceCollection();
services.AddDefaultAWSOptions(config.GetAWSOptions());
services.AddKeyedAWSService<IAmazonS3>(TestControllerKeyed.Key, new AWSOptions {Region = RegionEndpoint.EUCentral1 });

var serviceProvider = services.BuildServiceProvider();

var controller = ActivatorUtilities.CreateInstance<TestControllerKeyed>(serviceProvider);
Assert.NotNull(controller.S3Client);
Assert.Equal(RegionEndpoint.EUCentral1, controller.S3Client.Config.RegionEndpoint);
}

[Fact]
public void TryAddKeyedCanRegisterWithDefaultConfig()
{
var builder = new ConfigurationBuilder();
builder.AddJsonFile("./TestFiles/GetClientConfigSettingsTest.json");

IConfiguration config = builder.Build();

ServiceCollection services = new ServiceCollection();
services.AddDefaultAWSOptions(config.GetAWSOptions());
services.TryAddKeyedAWSService<IAmazonS3>(TestControllerKeyed.Key);

var serviceProvider = services.BuildServiceProvider();

var controller = ActivatorUtilities.CreateInstance<TestControllerKeyed>(serviceProvider);
Assert.NotNull(controller.S3Client);
Assert.Equal(RegionEndpoint.USWest2, controller.S3Client.Config.RegionEndpoint);
}

[Fact]
public void TryAddKeyedCanRegisterWithoutDefaultConfig()
{
ServiceCollection services = new ServiceCollection();
services.AddKeyedAWSService<IAmazonS3>(TestControllerKeyed.Key, new AWSOptions {Region = RegionEndpoint.USEast1 });

var serviceProvider = services.BuildServiceProvider();

var controller = ActivatorUtilities.CreateInstance<TestControllerKeyed>(serviceProvider);
Assert.NotNull(controller.S3Client);
Assert.Equal(RegionEndpoint.USEast1, controller.S3Client.Config.RegionEndpoint);
}

[Fact]
public void TryAddKeyedServiceDontOverrideWhenAlreadySetup()
{
var builder = new ConfigurationBuilder();
builder.AddJsonFile("./TestFiles/GetClientConfigSettingsTest.json");

IConfiguration config = builder.Build();

ServiceCollection services = new ServiceCollection();

services.AddDefaultAWSOptions(config.GetAWSOptions());
services.AddKeyedAWSService<IAmazonS3>(TestControllerKeyed.Key, new AWSOptions { Region = RegionEndpoint.EUWest1 });
services.TryAddKeyedAWSService<IAmazonS3>(TestControllerKeyed.Key);
services.TryAddKeyedAWSService<IAmazonS3>(TestControllerKeyed.Key, new AWSOptions { Region = RegionEndpoint.EUCentral1 });

var serviceProvider = services.BuildServiceProvider();

var controller = ActivatorUtilities.CreateInstance<TestControllerKeyed>(serviceProvider);
Assert.Equal(RegionEndpoint.EUWest1, controller.S3Client.Config.RegionEndpoint);
}

[Fact]
public void InjectMultipleS3Clients()
{
ServiceCollection services = new ServiceCollection();
services.AddKeyedAWSService<IAmazonS3>(TestControllerMultiKeyed.Key1, new AWSOptions { Region = RegionEndpoint.USEast1 });
services.AddKeyedAWSService<IAmazonS3>(TestControllerMultiKeyed.Key2, new AWSOptions { Region = RegionEndpoint.USWest2 });

var serviceProvider = services.BuildServiceProvider();

var controller = ActivatorUtilities.CreateInstance<TestControllerMultiKeyed>(serviceProvider);
Assert.NotNull(controller.S3Client1);
Assert.NotNull(controller.S3Client2);
Assert.Equal(RegionEndpoint.USEast1, controller.S3Client1.Config.RegionEndpoint);
Assert.Equal(RegionEndpoint.USWest2, controller.S3Client2.Config.RegionEndpoint);
}

public class TestControllerKeyed
{
public const string Key = "key";

public IAmazonS3 S3Client { get; private set; }
public TestControllerKeyed([FromKeyedServices(Key)] IAmazonS3 s3Client)
{
S3Client = s3Client;
}
}

public class TestControllerMultiKeyed
{
public const string Key1 = "key1";
public const string Key2 = "key2";

public IAmazonS3 S3Client1 { get; private set; }
public IAmazonS3 S3Client2 { get; private set; }
public TestControllerMultiKeyed(
[FromKeyedServices(Key1)] IAmazonS3 s3Client1,
[FromKeyedServices(Key2)] IAmazonS3 s3Client2)
{
S3Client1 = s3Client1;
S3Client2 = s3Client2;
}

}
#endif

public class TestController
{
Expand Down
11 changes: 10 additions & 1 deletion extensions/test/NETCore.SetupTests/NETCore.SetupTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,19 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Moq" Version="4.8.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' != 'net8.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="xunit" Version="2.4.2" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
</ItemGroup>



</Project>