Talking to Cloud Services

Call AWS or Azure from .NET with one shared client, registered once, using credentials the SDK finds for itself.

Syntaxbuilder.Services.AddAWSService<IAmazonS3>();

A cloud service is an HTTP API. An SDK, short for software development kit, is a set of libraries that wraps that API in ordinary C# types, so you call a method instead of building and signing a request by hand. AWS ships one NuGet package per service, such as AWSSDK.S3. Azure does the same, such as Azure.Storage.Blobs.

One client, shared

Each service has a client type: AmazonS3Client, BlobServiceClient. A client owns a pool of network connections and is safe to use from several threads at once, so you build one and share it for the life of the process. Register it as a singleton. For AWS, services.AddAWSService<IAmazonS3>() is shorthand for registering a singleton AmazonS3Client yourself. The two are the same thing, and the first worked example below shows the long form.

Credentials are found, not written

Neither SDK wants a key in your source. Each walks a chain of places a credential might live and uses the first that works. The AWS SDK looks at environment variables, then the shared credentials file, then the role attached to the container or machine. Azure's DefaultAzureCredential looks at environment variables, then workload identity, then managed identity, then a developer sign-in such as the Azure CLI. The same code runs on your laptop and in a pod with no change.

What trips people up

A client created per request leaks sockets. And because environment variables come first in the chain, one stale key left in the environment quietly overrides the role you attached.

Example

Example · csharp
// dotnet add package AWSSDK.S3
// dotnet add package AWSSDK.Extensions.NETCore.Setup
using Amazon;
using Amazon.Extensions.NETCore.Setup;
using Amazon.S3;
using Microsoft.Extensions.DependencyInjection;

ServiceCollection services = new();

// Region, and nothing else. No access key and no secret key anywhere.
services.AddDefaultAWSOptions(new AWSOptions
{
    Region = RegionEndpoint.EUWest1
});

// Registers AmazonS3Client as the implementation of IAmazonS3.
services.AddAWSService<IAmazonS3>();

// Inspect the registration. Nothing has contacted AWS yet.
foreach (ServiceDescriptor descriptor in services)
{
    if (descriptor.ServiceType == typeof(IAmazonS3))
    {
        Console.WriteLine($"{descriptor.ServiceType.Name}: {descriptor.Lifetime}");
    }
}

Console.WriteLine($"region: {RegionEndpoint.EUWest1.SystemName}");

// Output:
// IAmazonS3: Singleton
// region: eu-west-1

When to use it

  • Read a file out of S3 in an endpoint without building and signing the HTTP request yourself.
  • Give a service running on EKS access to a bucket through its IAM role instead of a key in a config file.
  • Point the same build at LocalStack on a laptop and at real AWS in production.

More examples

The long form of AddAWSService

Shorthand and longhand side by side. Reach for the long form when you need to set something on the client's config that AWSOptions does not expose, or when you want the registration visible in the same file as the rest of your services.

Example · csharp
using Amazon;
using Amazon.S3;
using Microsoft.Extensions.DependencyInjection;

ServiceCollection services = new();

// This is what AddAWSService<IAmazonS3>() does for you: one instance,
// built on first use, shared for the life of the process.
services.AddSingleton<IAmazonS3>(serviceProvider =>
    new AmazonS3Client(RegionEndpoint.EUWest1));

The Azure equivalent

Azure's SDK uses the same shape: a client per service, registered once, with the credential supplied separately. A GET to /health/storage returns {"containerExists":true} once the identity running the app has a role on the storage account.

Example · csharp
// dotnet add package Azure.Storage.Blobs
// dotnet add package Microsoft.Extensions.Azure
// dotnet add package Azure.Identity
using Azure.Identity;
using Azure.Storage.Blobs;
using Microsoft.Extensions.Azure;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddAzureClients(clients =>
{
    clients.AddBlobServiceClient(new Uri("https://contosofiles.blob.core.windows.net"));

    // One credential for every Azure client registered here.
    clients.UseCredential(new DefaultAzureCredential());
});

WebApplication app = builder.Build();

app.MapGet("/health/storage", async (BlobServiceClient blobs, CancellationToken ct) =>
{
    BlobContainerClient container = blobs.GetBlobContainerClient("uploads");
    bool exists = await container.ExistsAsync(ct);
    return Results.Ok(new { containerExists = exists });
});

app.Run();

Pointing at a local emulator

Use this for tests and local development so nothing reaches a real account. Keep it behind an environment check, and never put real keys in a BasicAWSCredentials call.

Example · csharp
using Amazon.Runtime;
using Amazon.S3;

// LocalStack and MinIO speak the S3 API on a local port. ServiceURL
// overrides the AWS endpoint; ForcePathStyle keeps the bucket name in
// the path instead of the hostname, which a local server expects.
AmazonS3Config config = new()
{
    ServiceURL = "http://localhost:4566",
    ForcePathStyle = true
};

// A local emulator accepts any key, so these are deliberately fake.
IAmazonS3 s3 = new AmazonS3Client(new BasicAWSCredentials("test", "test"), config);

Discussion

  • Be the first to comment on this lesson.