Operating Amazon S3 with ASP.NET Core Web API and Blazor


December 5, 2023 Program

Operating Amazon S3 with ASP.NET Core Web API and Blazor
Recording the Process of Integrating Amazon S3 with ASP.NET Core.

Foreword

๐Ÿ”—

The project structure for this article is Blazor WebAssembly Hybrid.Combining Blazor WebAssembly (Frontend) with Server API (Backend). Apart from documenting the usage of Web API for S3 integration, it also involves recording how Blazor WebAssembly calls the API to transfer files.

Environment

๐Ÿ”—
  • Blazor Webassembly Hybrid
  • .Net 6

In a typical Web API project, the structure is relatively straightforward. However, when utilizing the Blazor WebAssembly Hybrid approach, the project structure is divided into Client, Server, and Shared components. The Server part can essentially be treated similarly to a regular Web API project, encompassing structures like Controllers.

Blazor WebAssembly Hybrid Structure

Pre-process

๐Ÿ”—
  1. Install the corresponding version of AWSSDK.S3 Nuget package

AWSSDK.S3

Then you can perform operations on S3 using the AWS SDK and .NET applications.

  1. Get Access Key

This approach involves accessing AWS S3 through a third-party (Web API) and requires securely controlling access to AWS resources via AWS Identity and Access Management (IAM). After get Access Key that can implement S3 operation via API. (For details on acquiring these keys, please refer to the official website!)

  1. Set key to project

C# projects need to go to appsettings.json to set up. The setting structure of this project is as follows:

json
{
  "AWS": {
    "AccessKey": "Your AccessKey",
    "SecretKey": "Your SecretKey"
  }
}

Create S3 operation method

๐Ÿ”—
  1. Basic Settings
C#
using Amazon.Runtime;
using Amazon.S3.Transfer;
using Amazon.S3;
using Amazon.S3.Model;
using System.Web;

private IConfiguration configuration;
private readonly AwsCredentials awsCredentialsValues;
private readonly BasicAWSCredentials credentials;
private readonly AmazonS3Config config;

public StorageService()
{
    // Initializing configuration using appsettings.json file
    configuration = new ConfigurationBuilder()
                        .AddJsonFile("appsettings.json")
                        .Build();

    // Retrieving AWS section from the configuration
    var awsConfig = configuration.GetSection("AWS");

    // Retrieving AccessKey and SecretKey from the AWS section
    string accessKey = awsConfig["AccessKey"];
    string secretKey = awsConfig["SecretKey"];

    // Creating AWS credentials object
    awsCredentialsValues = new AwsCredentials()
    {
        AccessKey = accessKey,
        SecretKey = secretKey
    };

    // Creating BasicAWSCredentials object using AccessKey and SecretKey
    credentials = new BasicAWSCredentials(awsCredentialsValues.AccessKey, awsCredentialsValues.SecretKey);

    // Configuring Amazon S3 region endpoint to APNortheast1
    config = new AmazonS3Config()
    {
        RegionEndpoint = Amazon.RegionEndpoint.APNortheast1
    };
}

RegionEndpointNeed to be modified according to the service area.

  1. Create Folder
C#
public async Task<bool> CreateFolder(string folderPath)
{
    folderPath = HttpUtility.UrlDecode(folderPath);
    var s3client = new AmazonS3Client(credentials, config);

    PutObjectRequest request = new PutObjectRequest()
    {
        BucketName = "Your BucketName",
        Key = folderPath // in S3 key represents a path
    };

    PutObjectResponse response = await s3client.PutObjectAsync(request);

    if (response.HttpStatusCode == System.Net.HttpStatusCode.NoContent)
    {
        return true;
    }

    return false;
}

Uploaded object structure (can be set according to requirements)

C#
 public class S3Obj
    {
        public string Name { get; set; } = null!;
        public MemoryStream InputStream { get; set; } = null!;
        public string BucketName { get; set; } = null!;
    }
  1. Upload File
C#
public async Task<S3ResponseDto> UploadFileAsync(S3Obj obj)
{
    var response = new S3ResponseDto();
    try
    {
        var uploadRequest = new TransferUtilityUploadRequest()
        {
            InputStream = obj.InputStream,
            Key = obj.Name,
            BucketName = obj.BucketName,
            CannedACL = S3CannedACL.NoACL
        };

        // Initialize client
        using var client = new AmazonS3Client(credentials, config);

        // Initialize the transfer/upload tools
        var transferUtility = new TransferUtility(client);

        // Initiate the file upload
        await transferUtility.UploadAsync(uploadRequest);
        
        response.StatusCode = 201;
        response.Message = $"{obj.Name}";
    }
    catch (AmazonS3Exception s3Ex)
    {
        response.StatusCode = (int)s3Ex.StatusCode;
        response.Message = s3Ex.Message;
    }
    catch (Exception ex)
    {
        response.StatusCode = 500;
        response.Message = ex.Message;
    }
    return response;
}

Returned file object (can be set according to requirements)

C#
public class S3FileInfo
    {
        public string Url { get; set; } = string.Empty;
        public byte[]? File { get; set; } = null;
        public string Name { get; set; } = string.Empty;
        public string str_Id { get; set; } = string.Empty;
        public int Order { get; set; } = 0;
    }

Due to project requirements, need to get file URL and then displayed on the front end. To prevent leakage, you can use the pre-signed URL (to prevent the file from being freely downloaded and passed on), through GetPreSignedURL to obtain the pre-signed URL (Pre-signed URL) when retrieving the file.

Pre-signed URLs are a mechanism provided by Amazon S3 (Simple Storage Service) that allows access to specific resources in a bucket to be assigned in a secure and limited manner. This is a time-created URL that configures temporary access to a specific S3 resource (such as an object or file).

  1. Get Files
C#
public async Task<List<S3FileInfo>> GetFileAsync(ListObjectsV2Request request)
{
    // Initialize Amazon S3 client
    var s3client = new AmazonS3Client(credentials, config);

    // Get objects list based on the provided request
    var response = await s3client.ListObjectsV2Async(request);
    List<S3FileInfo> result = new List<S3FileInfo>();

    foreach (var obj in response.S3Objects)
    {
        // Skip if the object is a folder (ends with "/")
        if (obj.Key.EndsWith("/")) continue;

        // Generate a pre-signed URL for the object
        var presignRequest = new GetPreSignedUrlRequest()
        {
            BucketName = "Your BucketName",
            Key = obj.Key,
            Expires = DateTime.UtcNow.AddSeconds(86400),
        };
        var presignedUrlResponse = s3client.GetPreSignedURL(presignRequest);

        // The following content depends 
        //on the file's naming convention 
        //logic and the format of the returned file
        // Modify this logic according to your file 
        //naming conventions and desired file format

        // Extract file information 
        string name = Path.GetFileName(obj.Key);
        string[] info = name.Split('.');
        string str_ID = string.Empty;
        if (info.Length > 0) str_ID = info[0];

        // Add S3 file info to the result list
        result.Add(new S3FileInfo
        {
            Url = presignedUrlResponse,
            Name = obj.Key,
            str_Id = str_ID
        });
    }
    return result;
}

  1. Delete files
C#
public async Task<bool> DeleteFileAsync(string key)
{
    key = HttpUtility.UrlDecode(key);
    var s3client = new AmazonS3Client(credentials, config);
    var response = await s3client.DeleteObjectAsync("YourBucketName", key);
    if (response.HttpStatusCode == System.Net.HttpStatusCode.NoContent) 
    {
        return true;
    }
    return false;
}

The key is the path of the actual file. If you want to delete the folder, the key is ForderName/. If there are files under the folder, this method cannot delete the folder. You need to clear all the data advance that you can delete the folder.

The above process mainly implements several methods based on the current project requirements, including creating folders, uploading files, obtaining file URLs in a pre-signed manner, and deleting files.

The following step involves documenting the API code and how the frontend interacts with the API.

Blazor Server Side(Web API)

๐Ÿ”—
  1. Get file
C#
[HttpGet("product/{file}")]
public async Task<List<S3FileInfo>> GetProductFile(string file)
{
    var request = new ListObjectsV2Request()
    {
        BucketName = "YourBucketName",
        Prefix = $"YourFolderName/{file}"
    };

    List<S3FileInfo> FileInfo = await _storageService.GetFileAsync(request);
    return FileInfo;
}
  1. Upload file
C#
[HttpPost("product/{FileId}")]
public async Task<S3ResponseDto> UploadProductFile(IFormFile file, string FileId)
{
    //Prevent unauthorized users from uploading
    var authorizationHeader = HttpContext.Request.Headers["Authorization"];
    bool IsAuthentication = _authService.CheckToken(authorizationHeader);
    var result = new S3ResponseDto();

    if (IsAuthentication)
    {
        // Process file
        await using var memoryStream = new MemoryStream();
        await file.CopyToAsync(memoryStream);

        var fileExt = Path.GetExtension(file.FileName);
        var docName = $"{FileId}{fileExt}";
        
        // Call server
        var s3Obj = new S3Obj()
        {
            BucketName = "YourBucketName",
            InputStream = memoryStream,
            Name = "YourFolderName/" + docName
        };
        
        result = await _storageService.UploadFileAsync(s3Obj);
    }
    return result;
}

  1. Delete files
C#
[HttpDelete("{key}")]
public async Task<bool> Delete(string key)
{
    //Prevent unauthorized users from deleting
    var authorizationHeader = HttpContext.Request.Headers["Authorization"];
    bool IsAuthentication = _authService.CheckToken(authorizationHeader);
    
    if (IsAuthentication)
    {
        return await _storageService.DeleteFileAsync(key);
    }
    else
    {
        return false;
    }
}

Blazor Client Side

๐Ÿ”—

Register a StorageService service, corresponding to API functions such as getting, uploading and deleting files.

Code:

C#
public class StorageService : IStorageService
{
    private readonly HttpClient _http;

    public StorageService(HttpClient http)
    {
        _http = http;
    }

    public async Task<List<S3FileInfo>> GetProductFile(string file)
    {
        var result = await _http.GetFromJsonAsync<List<S3FileInfo>>($"api/Storage/product/{file}");
        return result;
    }

    public async Task<string> UploadFile(string FileId, string fileName, MemoryStream InputStream)
    {
        var response = await _http.PostAsync($"api/Storage/product/{FileId}",
            new MultipartFormDataContent {
                {
                    new StreamContent(InputStream), "file", fileName
                }
            });

        var newFileKey = (await response.Content.ReadFromJsonAsync<ServiceResponse<S3ResponseDto>>()).Message;
        return newFileKey;
    }

    public async Task DeleteProductImage(string key)
    {
        key = $"YourFolderName/{key}";
        string encodedKey = Uri.EscapeDataString(key);
        var result = await _http.DeleteAsync($"api/Storage/{encodedKey}");
    }
}

Conclusion

๐Ÿ”—

This article mainly records how to operate S3 through AWS SDK and .NET. The actual API settings and client connection can actually be more optimized to avoid the risk of Bucket structure leakage. Because the code is already the result of simplification of the original project, feel free to raise any questions or errors.

Reference

AWSBlazorWebCsharp



Avatar

Alvin

Software engineer, interested in financial knowledge, health concepts, psychology, independent travel, and system design.

Related Posts