The Right Way to Register AWS SDK Clients in .NET Dependency Injection
Registering AWS SDK clients with dependency injection can be confusing with multiple manual approaches. Let's explore the built-in AddAWSService method and when you need to fall back to manual registration.
Registering AWS SDK clients with dependency injection in .NET applications can quickly become messy with multiple manual registration styles scattered throughout your codebase.
You might have seen different patterns—some using singleton instances, others hardcoding credentials, and various ways of setting up regions.
In this post, let's explore the built-in mechanism that the AWS SDK provides for dependency injection and when falling back to manual registration might be necessary.
Thanks to AWS for sponsoring this article in my .NET on AWS Series.
The Problem with Manual Registration
Here's a typical ASP.NET API application with various manual dependency injection patterns for AWS services:
// Pattern 1: Manual registration with default credentialsbuilder.Services.AddSingleton<IAmazonDynamoDB, AmazonDynamoDBClient>();builder.Services.AddSingleton<IDynamoDBContext, DynamoDBContext>();// Pattern 2: Explicit instance with region overridebuilder.Services.AddSingleton<IAmazonS3>(new AmazonS3Client(RegionEndpoint.APSouth1));// Pattern 3: Hardcoded credentials (don't do this!)var credentials = new BasicAWSCredentials("ACCESS_KEY", "SECRET_KEY");builder.Services.AddSingleton<IAmazonSimpleNotificationService>(new AmazonSimpleNotificationServiceClient(credentials));
All of these work, but you end up with multiple styles and a lot of repetitive code. Each developer on the team might use a different pattern, making the codebase inconsistent.
There's also confusion about service lifetimes—should these be singleton, transient, or scoped?
The Solution: AddAWSService
The AWS SDK for .NET has built-in support for dependency injection through the AWSSDK.Extensions.NETCore.Setup NuGet package.
Let's add this package to get started:
dotnet add package AWSSDK.Extensions.NETCore.Setup
This package provides the AddAWSService<T>() extension method that simplifies client registration:
// Clean and consistent registrationbuilder.Services.AddAWSService<IAmazonDynamoDB>();builder.Services.AddAWSService<IAmazonS3>();builder.Services.AddAWSService<IAmazonSQS>();builder.Services.AddAWSService<IAmazonSimpleNotificationService>();
Much cleaner, right? All AWS service clients follow the same registration pattern.
AWS Lambda For The .NET Developer: How To Easily Get Started

How AddAWSService Works
The AddAWSService<T>() method does several things automatically:
- Credentials resolution: Uses the standard AWS credentials chain (appsettings.json → environment variables → local AWS config → IAM roles)
 - Region configuration: Automatically picks up the region from your environment
 - Service lifetime: Defaults to singleton registration (which is correct for AWS clients)
 
Let's look at what happens under the hood. When you call AddAWSService<IAmazonDynamoDB>(), it:
- Verifies the type implements 
IAmazonServiceinterface - Creates a service client using 
CreateServiceClient() - Uses the credentials chain to find appropriate credentials
 - Registers the client as a singleton in the DI container
 
If you're running locally, it uses the credentials configured in your AWS profile. If you're running in AWS (Lambda, EC2, ECS, etc.), it automatically uses the IAM role assigned to that service.
Service Lifetime and Thread Safety
One common question is: why singleton?
AWS SDK clients are thread-safe and designed to be reused. Creating a new client instance for each request is inefficient and can lead to connection exhaustion.
By default, AddAWSService<T>() registers all services as singletons, which is the recommended approach.
If for some specific reason you need a different lifetime, you can override it:
builder.Services.AddAWSService<IAmazonDynamoDB>(ServiceLifetime.Scoped);
However, in most cases, you should stick with the default singleton lifetime.
What About DynamoDBContext?
You might notice that AddAWSService<T>() only works with service clients that implement IAmazonService.
DynamoDB's higher-level IDynamoDBContext doesn't implement this interface—it's a wrapper around the low-level client.
For DynamoDB, you need to register both:
// Low-level clientbuilder.Services.AddAWSService<IAmazonDynamoDB>();// High-level context (manual registration still needed)builder.Services.AddSingleton<IDynamoDBContext, DynamoDBContext>();
The DynamoDBContext will automatically use the injected IAmazonDynamoDB client.
AWS DynamoDB For The .NET Developer: How To Easily Get Started

Advanced Scenarios
While AddAWSService<T>() works great for most scenarios, there are cases where you need to fall back to manual registration or combine different approaches.
When Manual Registration Is Necessary
Consider a scenario where most of your AWS resources are in ap-southeast-2 (Sydney), but you have an S3 bucket in ap-south-1 (Mumbai).
// Default services in Sydneybuilder.Services.AddAWSService<IAmazonDynamoDB>();builder.Services.AddAWSService<IAmazonSQS>();// S3 bucket specifically in Mumbaibuilder.Services.AddSingleton<IAmazonS3>(sp =>new AmazonS3Client(new AmazonS3Config{RegionEndpoint = RegionEndpoint.APSouth1}));
You might think you can use AddDefaultAWSOptions() to override the region:
var awsOptions = builder.Configuration.GetAWSOptions();awsOptions.Region = RegionEndpoint.APSouth1;builder.Services.AddDefaultAWSOptions(awsOptions);
However, this sets the region for all AWS services, not just S3. If you need different regions for different services, manual registration is your only option.
Practical Example
Let's look at a complete example with a Movies API that uses both DynamoDB and S3:
// Program.csbuilder.Services.AddAWSService<IAmazonDynamoDB>();builder.Services.AddSingleton<IDynamoDBContext, DynamoDBContext>();// S3 in different regionbuilder.Services.AddSingleton<IAmazonS3>(sp =>new AmazonS3Client(new AmazonS3Config{RegionEndpoint = RegionEndpoint.APSouth1}));
In the controller, you can inject these dependencies:
public class MoviesController(IDynamoDBContext dynamoDbContext, IAmazonS3 s3Client) : ControllerBase{[HttpGet]public async Task<IActionResult> GetMovies(int year){var movies = await dynamoDbContext.QueryAsync<Movie>(year).GetRemainingAsync();return Ok(movies);}[HttpPost]public async Task<IActionResult> CreateMovie([FromBody] Movie movie){// Save to DynamoDBawait dynamoDbContext.SaveAsync(movie);// Save metadata to S3 in Mumbai regionawait s3Client.PutObjectAsync(new PutObjectRequest{BucketName = "rahul-mumbai-region-bucket",Key = $"movies/{movie.Year}/{movie.Title}.json",ContentBody = JsonSerializer.Serialize(movie)});return Created();}}
This demonstrates both approaches—using AddAWSService<T>() for DynamoDB where default configuration works, and manual registration for S3 where we need a specific region.
Best Practices
Here are some common mistakes to avoid when working with AWS SDK dependency injection:
Avoid Hardcoding Credentials
Never hardcode AWS credentials in your source code:
// ❌ Don't do this!var credentials = new BasicAWSCredentials("AKIAIOSFODNN7EXAMPLE", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY");builder.Services.AddSingleton<IAmazonS3>(new AmazonS3Client(credentials));
Even storing them in appsettings.json is risky. Let the credentials chain handle this:
- Local development: Use AWS CLI profiles (
aws configure) - AWS environments: Use IAM roles attached to Lambda functions, EC2 instances, or ECS tasks
 
Use Consistent Service Lifetimes
Stick with singleton unless you have a specific reason to change it:
// ✅ Good - consistent singleton lifetimebuilder.Services.AddAWSService<IAmazonDynamoDB>();builder.Services.AddAWSService<IAmazonS3>();// ❌ Avoid - mixing lifetimes without clear reasonbuilder.Services.AddAWSService<IAmazonDynamoDB>(ServiceLifetime.Singleton);builder.Services.AddAWSService<IAmazonS3>(ServiceLifetime.Scoped);
Inject Clients Rather Than Creating Them
Avoid creating AWS clients directly in controllers or services:
// ❌ Don't do this![HttpGet]public async Task<IActionResult> GetMovies(){var client = new AmazonDynamoDBClient(); // Creates new client each time// ...}// ✅ Do this instead - inject the clientpublic class MoviesController : ControllerBase{private readonly IAmazonDynamoDB _dynamoDbClient;public MoviesController(IAmazonDynamoDB dynamoDbClient){_dynamoDbClient = dynamoDbClient;}}
Summary
For most scenarios, AddAWSService<T>() provides a clean, consistent way to register AWS SDK clients with dependency injection in .NET applications.
It handles credentials resolution, region configuration, and service lifetime management automatically, reducing boilerplate code and potential configuration errors.
Fall back to manual registration only when you need:
- Different regions for specific services
 - Special configuration requirements
 
If you found this helpful, make sure to subscribe to stay updated on more AWS and .NET content.