# Working With AWS S3 in C#

AWS `S3` (Simple storage service) is an AWS service that provides users the ability to store and retrieve static data seamlessly and efficiently. In this article, we are exploring how to use AWS S3 in a `.NET` application. The code for this article is available [here](https://github.com/JoshPhronesis/AwsS3InCsharp).

Firstly, some common terms in `S3`:

1. **Bucket:** A container for storing objects in Amazon S3. All objects are stored in a bucket. Each bucket has a globally unique name within Amazon S3.
    
2. **Object:** A unit of data stored in Amazon S3. An object consists of the data itself, a key (which is the unique identifier within a bucket), and metadata.
    
3. **Key:** The unique identifier for an object within a bucket. The combination of the bucket name and the object key forms the object's unique address.
    
4. **Metadata:** Additional information associated with an object, typically in the form of key-value pairs. Metadata provides details about the object, such as content type, creation date, etc.
    
5. **Versioning:** A feature that allows you to keep multiple versions of an object in the same bucket. This is useful for maintaining a version history and recovering from accidental deletions or overwrites.
    

Let's get started!

### Create an AWS Account

First and foremost, to use any AWS service, we need an AWS account. Go over to the website here and create an account.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692490551239/f5e9f905-6e66-481d-974f-3e1d0f40c5e1.png align="center")

### Create an S3 Bucket

1. Search for S3 on AWS and click on it to open the dashboard
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1693352609575/7f5f46b4-ca10-47d1-8df7-a7a905202c8f.png align="center")
    
2. Click on `Create bucket` to create a new bucket
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1693352792303/8a263eeb-663e-4bc5-805a-2e1e14bbc27e.png align="center")
    
3. Give your bucket a name and save. *(We would leave other default settings as is.)*
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1693352835675/feb7d6e3-def5-49b4-928c-cc5b724cb8f8.png align="center")
    

### Call your S3 bucket via your `.NET` application

Our `.NET` application would be an `Asp.net` API responsible for uploading customer's profile pictures.

1. Create a new Asp.net core API project
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1693353800723/41c65648-4d78-4f73-a750-bd18b6e8a9f8.png align="center")
    
2. App `AWSSDK.S3` NuGet package to the project
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1693353934000/91a7b2cf-3088-4cf7-a95d-2e448519affd.png align="center")
    
3. Now, we will implement our code.
    
    * Add the following file structure
        
        ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1693986637720/be82ac27-822a-4773-a07a-3daa2ddbbc30.png align="center")
        
    * Add the following code to their respective files:
        
        * `ProfilePictureController.cs`
            
            ```csharp
            using Microsoft.AspNetCore.Mvc;
            
            namespace Customers.Api.Controllers;
            
            [Route("api/user/{userId:guid}/profile-picture")]
            [ApiController]
            public class ProfilePictureController : ControllerBase
            {
                private readonly IStorageService _storageService;
            
                public ProfilePictureController(IStorageService storageService)
                {
                    _storageService = storageService;
                }
            
                [HttpPost]
                public async Task<IActionResult> SavePicture([FromForm] IFormFile image, [FromRoute] Guid userId)
                {
                    var result = await _storageService.UploadImageAsync(image, userId);
                    return result switch
                    {
                        true => CreatedAtAction(nameof(GetImage), new { userId }, new {}),
                        _ => BadRequest()
                    };
                }
            
                [HttpGet(Name = nameof(GetImage))]
                public async Task<IActionResult> GetImage([FromRoute] Guid userId)
                {
                    var result = await _storageService.GetImageAsync(userId);
                    if (result.responseStream is not {})
                    {
                        return NotFound();
                    }
            
                    return File(result.responseStream, result.contentType);
                }
            
                [HttpDelete]
                public async Task<IActionResult> DeleteImage([FromRoute] Guid userId)
                {
                    var result = await _storageService.RemoveImageAsync(userId);
            
                    return result switch
                    {
                        true => NoContent(),
                        _ => BadRequest()
                    };
                }
            }
            ```
            
        * `IStorageService.cs`
            
            ```csharp
            namespace Customers.Api;
            
            public  interface IStorageService
            {
                Task<bool> RemoveImageAsync(Guid id);
                Task<(Stream? responseStream, string contentType)> GetImageAsync(Guid id);
                Task<bool> UploadImageAsync(IFormFile image, Guid id);
            }
            ```
            
        * `StorageService.cs`
            
            ```csharp
            using System.Net;
            using Amazon.S3;
            using Amazon.S3.Model;
            using Ardalis.GuardClauses;
            using Microsoft.Extensions.Options;
            
            namespace Customers.Api;
            
            public class StorageService : IStorageService
            {
                private readonly IAmazonS3 _amazonS3Client;
                private readonly IOptions<StorageConfig> _storageOptions;
                private readonly ILogger<StorageService> _logger;
            
                public StorageService(IAmazonS3 amazonS3Client, 
                    IOptions<StorageConfig> storageOptions,
                    ILogger<StorageService> logger)
                {
                    _amazonS3Client = amazonS3Client;
                    _storageOptions = storageOptions;
                    _logger = logger;
                }
            
                public async Task<bool> RemoveImageAsync(Guid Id)
                {
                    try
                    {
                        Guard.Against.Default(Id);
                        var request = new DeleteObjectRequest()
                        {
                            BucketName = _storageOptions.Value.S3Bucket,
                            Key = $"profile-picture/{Id}",
                        };
            
                        var response = await _amazonS3Client.DeleteObjectAsync(request);
                        if (response.HttpStatusCode == HttpStatusCode.NoContent)
                        {
                            return true;
                        }
                    }
                    catch (Exception e)
                    {
                        _logger.LogError(e, $"an error has occured in {nameof(RemoveImageAsync)}");
                    }
            
                    return false;
                }
            
                public async Task<(Stream? responseStream, string contentType)> GetImageAsync(Guid Id)
                {
                    try
                    {
                        Guard.Against.Default(Id);
            
                        var request = new GetObjectRequest()
                        {
                            BucketName = _storageOptions.Value.S3Bucket,
                            Key = $"profile-picture/{Id}",
                        };
            
                        var response = await _amazonS3Client.GetObjectAsync(request);
                        if (response.HttpStatusCode == HttpStatusCode.OK)
                        {
                            return (responseStream: response.ResponseStream, contentType: response.Headers.ContentType);
                        }
                    }
                    catch (Exception e)
                    {
                        _logger.LogError(e, $"an error has occured in {nameof(GetImageAsync)}");
                    }
            
                    return default;
                }
            
                public async Task<bool> UploadImageAsync(IFormFile image, Guid id)
                {
                    try
                    {
                        Guard.Against.Null(image);
                        Guard.Against.Default(id);
            
                        var putObjectRequest = new PutObjectRequest()
                        {
                            BucketName = _storageOptions.Value.S3Bucket,
                            Key = $"profile-picture/{id}",
                            InputStream = image.OpenReadStream(),
                            ContentType = image.ContentType,
                            Metadata =
                            {
                                ["x-amz-meta-original-file-name"] = image.FileName,
                                ["x-amz-meta-original-file-extension"] = Path.GetExtension(image.FileName),
                            }
                        };
                        var response = await _amazonS3Client.PutObjectAsync(putObjectRequest);
                        if (response.HttpStatusCode == HttpStatusCode.OK)
                        {
                            return true;
                        }
                    }
                    catch (Exception e)
                    {
                        _logger.LogError(e, $"an error has occured in {nameof(UploadImageAsync)}");
                    }
            
                    return false;
                }
            }
            ```
            
        * `appsettings.json`
            
            ```json
            {
              "Logging": {
                "LogLevel": {
                  "Default": "Information",
                  "Microsoft.AspNetCore": "Warning"
                }
              },
              "AllowedHosts": "*",
              "StorageConfig": {
                "S3Bucket" : "**YOUR S3 BUCKET NAME**"
              }
            }
            ```
            
        * `Program.cs`
            
            ```csharp
            using Amazon.S3;
            using Customers.Api;
            
            var builder = WebApplication.CreateBuilder(args);
            
            builder.Services.AddControllers();
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();
            builder.Services.AddScoped<IAmazonS3, AmazonS3Client>();
            builder.Services.AddScoped<IStorageService, StorageService>();
            builder.Services.Configure<StorageConfig>(builder.Configuration.GetSection("StorageConfig"));
            var app = builder.Build();
            
            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }
            
            app.UseAuthorization();
            app.MapControllers();
            app.Run();
            ```
            

That's it! Now, let's test our implementation

---

Run the just-created app and you should see a swagger documentation page like this:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1693987061923/3fdafd3c-3899-4eea-ac10-0a98050bf901.png align="center")

To test our code, we would use Postman to call our API endpoints.

* Calling our endpoint with a `POST` request, we receive a `201` response code, indicating our image has been successfully uploaded
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1693987333084/91007168-269b-490a-8b73-295a2cf2b9df.png align="center")
    
* Calling our endpoint with a `GET` request, we receive a `200` response code, along with the image
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1693987397157/c8a657be-54cf-4c83-ab6c-8a1697218458.png align="center")
    
* And finally, calling our endpoint with a `DELETE` request, we received a `204` response code, indicating our image has been successfully deleted
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1693987459933/f3260785-354a-43d5-b370-e5cbc0b9949a.png align="center")
    

And that's it! The code for this article is available [here](https://github.com/JoshPhronesis/AwsS3InCsharp).

Until next time, Cheers!
