Table of Contents

Object Storage — CL.StorageS3

CL.StorageS3 provides unified access to Amazon S3 and MinIO-compatible object storage. It supports upload, download, delete, listing, presigned URLs, and bucket management.


Registration

await Libraries.LoadAsync<CL.StorageS3.StorageS3Library>();

Configuration (config.storage.json)

Amazon S3

{
  "Provider": "S3",
  "Region": "eu-west-1",
  "AccessKey": "AKIAIOSFODNN7EXAMPLE",
  "SecretKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
  "DefaultBucket": "my-app-storage"
}

MinIO

{
  "Provider": "MinIO",
  "Endpoint": "minio.internal:9000",
  "AccessKey": "minioadmin",
  "SecretKey": "minioadmin",
  "UseSSL": false,
  "DefaultBucket": "my-app-storage"
}

Uploading Objects

var storage = context.GetLibrary<CL.StorageS3.StorageS3Library>();

// Upload from bytes
await storage.PutAsync("uploads/avatar-123.jpg", imageBytes, "image/jpeg");

// Upload from stream
await using var stream = File.OpenRead("report.pdf");
await storage.PutAsync("reports/2026-04.pdf", stream, "application/pdf");

// Upload to a specific bucket
await storage.PutAsync(
    key:         "backups/db-2026-04-01.sql.gz",
    data:        backupBytes,
    contentType: "application/gzip",
    bucket:      "my-app-backups"
);

Downloading Objects

// Download to bytes
byte[] data = await storage.GetAsync("uploads/avatar-123.jpg");

// Download to stream
await using var outStream = File.Create("local-copy.pdf");
await storage.GetToStreamAsync("reports/2026-04.pdf", outStream);

// Check existence
bool exists = await storage.ExistsAsync("uploads/avatar-123.jpg");

Deleting Objects

// Delete a single object
await storage.DeleteAsync("uploads/old-avatar.jpg");

// Delete multiple objects
await storage.DeleteManyAsync([
    "uploads/temp-1.jpg",
    "uploads/temp-2.jpg",
    "cache/stale-data.json"
]);

Listing Objects

// List all objects with prefix
var objects = await storage.ListAsync("uploads/");

foreach (var obj in objects)
{
    Console.WriteLine($"{obj.Key}  {obj.Size:N0} bytes  {obj.LastModified:yyyy-MM-dd}");
}

// List with pagination
var page = await storage.ListAsync("reports/", maxKeys: 100, continuationToken: nextToken);

Presigned URLs

Generate temporary URLs for direct client access:

// Download URL (valid for 1 hour)
var downloadUrl = await storage.GetPresignedUrlAsync(
    key:     "uploads/avatar-123.jpg",
    expires: TimeSpan.FromHours(1),
    method:  PresignedMethod.Get
);

// Upload URL (client uploads directly to S3/MinIO)
var uploadUrl = await storage.GetPresignedUrlAsync(
    key:     "uploads/new-avatar.jpg",
    expires: TimeSpan.FromMinutes(15),
    method:  PresignedMethod.Put
);

Return uploadUrl to the client — they POST/PUT directly to S3 without going through your server.


Bucket Management

// Create bucket
await storage.CreateBucketAsync("my-new-bucket");

// Check if bucket exists
bool exists = await storage.BucketExistsAsync("my-new-bucket");

// List all buckets
var buckets = await storage.ListBucketsAsync();

// Delete bucket (must be empty)
await storage.DeleteBucketAsync("my-old-bucket");

Object Metadata

// Attach metadata on upload
await storage.PutAsync("uploads/doc.pdf", pdfBytes, "application/pdf",
    metadata: new Dictionary<string, string>
    {
        ["uploaded-by"] = "user-123",
        ["original-name"] = "Q1 Report.pdf"
    });

// Read metadata
var info = await storage.GetMetadataAsync("uploads/doc.pdf");
Console.WriteLine(info.ContentType);   // application/pdf
Console.WriteLine(info.Size);          // bytes
Console.WriteLine(info.Metadata["uploaded-by"]);   // user-123

Health Check

// Returns Healthy if the default bucket is accessible
// Returns Unhealthy if credentials are invalid or endpoint is unreachable
var status = await storage.HealthCheckAsync();