S3Storage.Client
1.0.6
dotnet add package S3Storage.Client --version 1.0.6
NuGet\Install-Package S3Storage.Client -Version 1.0.6
<PackageReference Include="S3Storage.Client" Version="1.0.6" />
<PackageVersion Include="S3Storage.Client" Version="1.0.6" />
<PackageReference Include="S3Storage.Client" />
paket add S3Storage.Client --version 1.0.6
#r "nuget: S3Storage.Client, 1.0.6"
#:package S3Storage.Client@1.0.6
#addin nuget:?package=S3Storage.Client&version=1.0.6
#tool nuget:?package=S3Storage.Client&version=1.0.6
S3Storage.Client
S3Storage.Client is a lightweight, dependency-free .NET client for AWS S3 and S3-compatible
object storage providers. It signs every request with AWS Signature V4 and works with any
provider that implements the S3 API.
Supported providers
| Provider | Factory method |
|---|---|
| AWS S3 | S3ClientFactory.ForAwsS3 |
| MinIO | S3ClientFactory.ForMinIO |
| RustFS | S3ClientFactory.ForRustFS |
| Cloudflare R2 | S3ClientFactory.ForCloudflareR2 |
| DigitalOcean Spaces | S3ClientFactory.ForDigitalOceanSpaces |
| Backblaze B2 | S3ClientFactory.ForBackblazeB2 |
| Wasabi | S3ClientFactory.ForWasabi |
| Yandex Object Storage | S3ClientFactory.ForYandex |
| Linode (Akamai) Object Storage | S3ClientFactory.ForLinode |
| Alibaba Cloud OSS | S3ClientFactory.ForAlibabaOSS |
| Oracle Cloud Object Storage | S3ClientFactory.ForOracleObjectStorage |
| Google Cloud Storage (HMAC) | S3ClientFactory.ForGoogleCloudStorage |
| MinIO Operator (Kubernetes) | S3ClientFactory.ForMinIOOperator |
| LocalStack | S3ClientFactory.ForLocalStack |
| Custom endpoint | S3ClientFactory.ForCustom |
Install
dotnet add package S3Storage.Client
Quick start
using S3Storage;
using var client = S3ClientFactory.ForMinIO(
host: "localhost",
accessKey: "minioadmin",
secretKey: "minioadmin",
port: 9000,
useSSL: false);
await client.CreateBucketAsync("demo-bucket");
await client.UploadTextAsync("demo-bucket", "hello.txt", "Hello from S3Storage.Client!");
var text = await client.DownloadTextAsync("demo-bucket", "hello.txt");
Console.WriteLine(text);
ASP.NET Core dependency injection
Register the client as a singleton so the internal HttpClient connection pool is reused:
builder.Services.AddSingleton<IS3Client>(_ =>
S3ClientFactory.ForMinIO(
host: builder.Configuration["S3:Host"]!,
accessKey: builder.Configuration["S3:AccessKey"]!,
secretKey: builder.Configuration["S3:SecretKey"]!,
port: int.Parse(builder.Configuration["S3:Port"] ?? "9000"),
useSSL: bool.Parse(builder.Configuration["S3:UseSSL"] ?? "false"),
region: builder.Configuration["S3:Region"] ?? "us-east-1"));
appsettings.json:
{
"S3": {
"Host": "localhost",
"Port": "9000",
"AccessKey": "minioadmin",
"SecretKey": "minioadmin",
"UseSSL": "false",
"Region": "us-east-1"
}
}
Common operations
Bucket management
await client.CreateBucketAsync("my-bucket");
bool exists = await client.BucketExistsAsync("my-bucket");
List<string> buckets = await client.ListBucketsAsync();
await client.DeleteBucketAsync("my-bucket"); // bucket must be empty
Upload
// From a local file (content-type inferred from extension)
await client.UploadFileAsync("my-bucket", "reports/q3.pdf", @"C:\data\q3.pdf");
// From a stream
await using var stream = File.OpenRead(@"C:\data\photo.jpg");
await client.UploadAsync("my-bucket", "photos/photo.jpg", stream, "image/jpeg");
// Plain text
await client.UploadTextAsync("my-bucket", "notes/readme.txt", "Hello!");
// Raw bytes
byte[] data = ...;
await client.UploadBytesAsync("my-bucket", "binary/data.bin", data);
Download
byte[] bytes = await client.DownloadBytesAsync("my-bucket", "binary/data.bin");
string text = await client.DownloadTextAsync("my-bucket", "notes/readme.txt");
await client.DownloadFileAsync("my-bucket", "reports/q3.pdf", @"C:\downloads\q3.pdf");
Object metadata and listing
bool exists = await client.ObjectExistsAsync("my-bucket", "notes/readme.txt");
ObjectInfo info = await client.GetObjectInfoAsync("my-bucket", "notes/readme.txt");
Console.WriteLine($"{info.Key} {info.Size} bytes {info.ContentType}");
// List up to 1 000 keys; optionally filter by prefix
List<ObjectInfo> objects = await client.ListObjectsAsync("my-bucket", prefix: "reports/");
Delete
await client.DeleteObjectAsync("my-bucket", "notes/readme.txt");
// Batch delete — up to 1 000 keys per call; empty list is a no-op
await client.DeleteObjectsAsync("my-bucket", new[] { "a.txt", "b.txt", "c.txt" });
Presigned URL
// Generate a GET URL valid for one hour (default) — no credentials required to use it
string url = client.GetPresignedUrl("my-bucket", "reports/q3.pdf", expirySeconds: 3600);
Multipart upload (large files)
For files larger than a few hundred MB, use the high-level MultipartUploadAsync helper.
It reads the stream in parallel parts, retries nothing on failure but aborts cleanly:
await using var stream = File.OpenRead(@"C:\data\archive.zip");
await client.MultipartUploadAsync(
bucket: "my-bucket",
key: "archive.zip",
data: stream,
contentType: "application/zip",
partSize: 8 * 1024 * 1024, // 8 MB per part (minimum 5 MB)
parallelism: 4); // concurrent part uploads (max 16)
For manual control (e.g. resumable uploads), use the low-level API:
string uploadId = await client.CreateMultipartUploadAsync("my-bucket", "large.bin");
var eTags = new Dictionary<int, string>();
try
{
eTags[1] = await client.UploadPartAsync("my-bucket", "large.bin", uploadId, 1, part1Stream);
eTags[2] = await client.UploadPartAsync("my-bucket", "large.bin", uploadId, 2, part2Stream);
await client.CompleteMultipartUploadAsync("my-bucket", "large.bin", uploadId, eTags);
}
catch
{
await client.AbortMultipartUploadAsync("my-bucket", "large.bin", uploadId);
throw;
}
Error handling
All S3 errors are surfaced as S3Exception. The message includes the HTTP status code and the
provider's error code (e.g. [404] NoSuchKey: The specified key does not exist.).
try
{
var text = await client.DownloadTextAsync("my-bucket", "missing.txt");
}
catch (S3Exception ex)
{
Console.WriteLine(ex.Message);
}
Security notes
- Every request is signed with AWS Signature Version 4.
- Always use HTTPS (
useSSL: true) in production environments. - Register the client as a DI singleton — the internal
SocketsHttpHandlermaintains a connection pool that is reused across calls, reducing latency and avoiding port exhaustion. - Never hard-code credentials; read them from environment variables,
IConfiguration, or a secrets manager.
License
MIT License. See LICENSE.
Copyright (c) 2026 Aktam Abdunazarov, AbdunazarovLabs
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.6 | 119 | 3/31/2026 |