Advanced library for AWS S3 storage operations and cloud file management. Provides async CRUD operations, strongly-typed S3 object handling, presigned URLs, bucket management, and enterprise-ready cloud storage patterns for cloud-native applications.
$ dotnet add package Acontplus.S3ApplicationProduction-ready AWS S3 storage library with enterprise-grade scalability, resilience, and performance optimizations. Built for high-throughput cloud-native applications.
ConcurrentDictionary for multi-threaded workloadsIOptions<T> patternInstall-Package Acontplus.S3Application -Version 2.0.0
dotnet add package Acontplus.S3Application --version 2.0.0
<PackageReference Include="Acontplus.S3Application" Version="2.0.0" />
using Acontplus.S3Application.Extensions;
// In Program.cs or Startup.cs
var builder = WebApplication.CreateBuilder(args);
// Option 1: Load configuration from appsettings.json (recommended)
builder.Services.AddS3Storage(builder.Configuration);
// Option 2: Configure explicitly
builder.Services.AddS3Storage(options =>
{
options.MaxRequestsPerSecond = 100;
options.TimeoutSeconds = 60;
options.MaxRetries = 3;
options.RetryBaseDelayMs = 500;
options.Region = "us-east-1";
});
// Option 3: Use all defaults
builder.Services.AddS3Storage();
{
"AWS": {
"S3": {
"MaxRequestsPerSecond": 100,
"TimeoutSeconds": 60,
"MaxRetries": 3,
"RetryBaseDelayMs": 500,
"Region": "us-east-1",
"DefaultBucketName": "my-bucket",
"ForcePathStyle": false,
"EnableMetrics": false
},
"AccessKey": "AKIA...",
"SecretKey": "secret..."
}
}
Note:
AWS:S3 settings are optional with sensible defaultsAWS:AccessKey/SecretKey for credentials (modern approach)S3Bucket, AwsConfiguration) are supported for backward compatibility but deprecated - avoid in new projectspublic class FileUploadController : ControllerBase
{
private readonly IS3StorageService _s3Service;
private readonly IConfiguration _configuration;
public FileUploadController(IS3StorageService s3Service, IConfiguration configuration)
{
_s3Service = s3Service;
_configuration = configuration;
}
[HttpPost("upload")]
public async Task<IActionResult> Upload(IFormFile file)
{
var s3Object = new S3ObjectCustom(_configuration);
await s3Object.Initialize("uploads/", file);
var response = await _s3Service.UploadAsync(s3Object);
return response.StatusCode == 201
? Ok(new { message = response.Message, url = s3Object.S3ObjectUrl })
: StatusCode(response.StatusCode, response.Message);
}
}
All settings are optional with production-ready defaults:
| Setting | Default | Description |
|---|---|---|
MaxRequestsPerSecond | 100 | Rate limit to prevent AWS throttling |
TimeoutSeconds | 60 | Request timeout in seconds |
MaxRetries | 3 | Retry attempts for transient failures |
RetryBaseDelayMs | 500 | Base delay for exponential backoff |
Region | null | AWS region (falls back to S3Bucket:Region) |
DefaultBucketName | null | Default bucket for operations |
ForcePathStyle | false | Use path-style URLs (S3-compatible services) |
EnableMetrics | false | Enable detailed request metrics |
Credentials can be configured via unified keys (recommended) or legacy keys (deprecated):
| Unified Key (Use This) | Legacy Key (Deprecated) | Required | Description |
|---|---|---|---|
AWS:AccessKey | AwsConfiguration:AWSAccessKey | No* | AWS access key ID |
AWS:SecretKey | AwsConfiguration:AWSSecretKey | No* | AWS secret access key |
AWS:S3:DefaultBucketName | S3Bucket:Name | Yes** | S3 bucket name |
AWS:S3:Region | S3Bucket:Region | No | AWS region (default: us-east-1) |
⚠️ Deprecation Notice: Legacy keys (
S3Bucket:*,AwsConfiguration:*) are maintained for backward compatibility but should not be used in new projects. They will be removed in v3.0.0.
* Credentials are optional when using:
~/.aws/credentials)** Required only when using S3ObjectCustom with IConfiguration
Recommended (unified keys):
{
"AWS": {
"S3": {
"DefaultBucketName": "my-bucket",
"Region": "us-east-1"
},
"AccessKey": "AKIA...",
"SecretKey": "secret..."
}
}
⚠️ Legacy keys (deprecated - avoid in new projects):
{
"S3Bucket": {
"Name": "my-bucket",
"Region": "us-east-1"
},
"AwsConfiguration": {
"AWSAccessKey": "AKIA...",
"AWSSecretKey": "secret..."
}
}
IAM Roles (no credentials needed):
{
"AWS": {
"S3": {
"DefaultBucketName": "my-bucket",
"Region": "us-east-1"
}
}
}
High-Throughput Workload:
{
"AWS": {
"S3": {
"MaxRequestsPerSecond": 200,
"MaxRetries": 5,
"TimeoutSeconds": 120
}
}
}
S3-Compatible Services (MinIO, DigitalOcean Spaces):
{
"AWS": {
"S3": {
"ForcePathStyle": true,
"Region": "us-east-1"
}
}
}
var s3Object = new S3ObjectCustom(_configuration);
s3Object.Initialize("uploads/invoice-2024.pdf");
var response = await _s3Service.GetObjectAsync(s3Object);
if (response.StatusCode == 200)
{
return File(response.Content, response.ContentType, response.FileName);
}
var s3Object = new S3ObjectCustom(_configuration);
s3Object.Initialize("uploads/report.pdf");
var urlResponse = await _s3Service.GetPresignedUrlAsync(s3Object, expirationInMinutes: 30);
if (urlResponse.StatusCode == 200)
{
var presignedUrl = urlResponse.FileName;
// Share URL with users - expires in 30 minutes
}
var exists = await _s3Service.DoesObjectExistAsync(s3Object);
if (exists)
{
// File exists in S3
}
var response = await _s3Service.DeleteAsync(s3Object);
if (response.StatusCode == 200)
{
// Successfully deleted
}
| Metric | v1.x | v2.0.0 | Improvement |
|---|---|---|---|
| Connections/second | 10-20 | 100-500 | 25x faster |
| Memory per request | ~5MB | ~500KB | 90% reduction |
| Avg latency (pooled) | 150-300ms | 50-100ms | 66% faster |
| CPU usage | High | Low-Medium | 60% reduction |
| Transient error recovery | ❌ Manual | ✅ Automatic | Built-in |
The service automatically enforces configured rate limits:
// This will automatically throttle to 100 req/s (default)
var tasks = Enumerable.Range(0, 500)
.Select(i => _s3Service.UploadAsync(objects[i]));
await Task.WhenAll(tasks); // Completes in ~5 seconds with smooth throttling
Clients are pooled by credentials + region:
// First call: Creates new client
await _s3Service.UploadAsync(s3Object); // ~200ms
// Subsequent calls: Reuses pooled client
await _s3Service.UploadAsync(s3Object2); // ~50ms (4x faster!)
Breaking Changes:
IOptions<S3StorageOptions> and ILogger<S3StorageService>Migration Steps:
// OLD (v1.x) - Remove this
services.AddScoped<IS3StorageService, S3StorageService>();
// NEW (v2.0.0) - Add this
services.AddS3Storage(configuration);
Benefits:
See UPGRADE_GUIDE_v2.0.0.md for complete migration instructions.
All service methods return an S3Response object with comprehensive error information:
public class S3Response
{
public int StatusCode { get; set; } // HTTP-like: 200, 201, 404, 500
public string Message { get; set; } // Success or error message
public byte[]? Content { get; set; } // File bytes (downloads)
public string? ContentType { get; set; } // MIME type
public string? FileName { get; set; } // File name or presigned URL
}
The service automatically retries these transient errors:
Retry Schedule (default):
The service logs all operations with structured context:
// Successful operations
_logger.LogInformation("Successfully uploaded {Key} to bucket {Bucket}",
"uploads/file.pdf", "my-bucket");
// Retry attempts
_logger.LogWarning("S3 operation retry {RetryCount}/{MaxRetries} after {Delay}. Error: SlowDown",
2, 3, "1s");
// Errors
_logger.LogError("S3 error uploading {Key}: {ErrorCode} - {Message}",
"uploads/file.pdf", "AccessDenied", "Insufficient permissions");
Contributions are welcome! Please open an issue or submit a pull request.
MIT License. See LICENSE for details.