Local .NET Development with Amazon S3
When developing applications that integrate with cloud services, I find it to be a more pleasant experience if I can raise, debug and destroy instances of these services locally as I build my application. In the case of Amazon S3, I might have a number of files I need to be moving around during development, and I don’t want to wait while transferring these files between my local machine and the cloud.
LocalStack to the rescue
LocalStack offers a number of Amazon APIs that can be brought up on your local machine. Importantly, it also comes in a container and so Amazon APIs on our local are only a docker-compose
away. Here’s what it looks like;
version: '3.2'
services:
localstack:
image: localstack/localstack:latest
container_name: localstack
ports:
- 4566:4566
environment:
- SERVICES=s3
- DEBUG=1
- DATA_DIR=/tmp/localstack/data
volumes:
- ./.localstack:/tmp/localstack
- /var/run/docker.sock:/var/run/docker.sock
Running docker-compose up
on the above will bring the services (in this case just S3) up and if you browse to http://localhost:4566 you’ll get the following message, which means you’re ready to go;
{"status": "running"}
Note that LocalStack used to have a web UI, but it’s deprecated, doesn’t seem to do much, and you probably don’t need it. It is still available via localstack/localstack-full:latest
, but I wouldn’t bother.
Setting up a bucket
Now that LocalStack is running, we can use the AWS CLI tool to configure a bucket;
aws --endpoint-url=http://localhost:4566 s3 mb s3://my-bucket
aws --endpoint-url=http://localhost:4566 s3api put-bucket-acl --bucket my-bucket --acl public-read
aws --endpoint-url=http://localhost:4566 s3 cp ~/Desktop/some-image.jpg s3://my-bucket/image.jpg
The lines above create the bucket, set a public-read
ACL on it so subsequently uploaded files will be accessible, and, the last line copies a file into the bucket.
At this point we can now pop http://localhost:4566/my-bucket/image.jpg into our browser and we should be able to see the image we uploaded. If we didn’t execute the second line, or, we executed it after uploading the file, browsing to that key will return HTTP Status Code 403 (Access Denied).
Integrating with .NET Core
With the service running and the bucket configured we’re ready to integrate. We can add AWSSDK.S3 to our .NET Core project with the following;
dotnet add package AWSSDK.S3
Using native dependency injection, and prior to pointing at our local, we might configure IAmazonS3
as follows;
services
.AddSingleton<IAmazonS3>(p => {
var config = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USWest2,
};
return new AmazonS3Client(myAccessKey, mySecret, config);
});
To point this configuration instead at our LocalStack instance we’ll edit the config
object we pass to Amazons3Client
with the following;
if (p.GetService<IHostEnvironment>().IsDevelopment())
{
config.ForcePathStyle = true;
config.ServiceURL = "http://localhost:4566";
}
We need to set ServiceURL
to LocalStack and set ForcePathStyle
to true
. Additionally, we do this only for the Development
environment, meaning when we publish as Production
, we’ll point at Amazon S3 rather than LocalStack. The completed code looks like this;
services
.AddSingleton<IAmazonS3>(p => {
var config = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.USWest2,
};
if (p.GetService<IHostEnvironment>().IsDevelopment())
{
config.ForcePathStyle = true;
config.ServiceURL = options.Url;
}
return new AmazonS3Client(myAccessKey, mySecret, config);
});
Regardless of whether we are integrating with Amazon S3 or LocalStack on our local, the mechanism to transfer files remains unchanged;
public async Task<IActionResult> Upload([FromServices] IAmazonS3 s3)
{
await new TransferUtility(s3)
.UploadAsync(source, "my-bucket", "some/path/image.jpg");
return Ok();
}
Conclusion
Wrapping it up, I’ve used LocalStack specifically for S3 a couple of times and the points are to ignore the web UI - you don’t need it - and make sure to set ForcePathStyle
and the ServiceURL
when integrating with .NET.