Downloading Objects
Download objects from R2 buckets to local files or streams.
Overview
public class DownloadService(IR2Client r2)
{
public async Task DownloadAsync(string key, string localPath)
{
var result = await r2.DownloadFileAsync("my-bucket", key, localPath);
Console.WriteLine($"Downloaded {result.EgressBytes} bytes");
Console.WriteLine($"Class B operations: {result.ClassBOperations}");
}
}
Download to File
var result = await r2.DownloadFileAsync(
bucketName: "my-bucket",
objectKey: "documents/report.pdf",
downloadPath: "/local/path/report.pdf");
Console.WriteLine($"Downloaded: {result.EgressBytes} bytes");
Download to Stream
Download directly to a stream for processing:
await using var fileStream = File.Create("/local/path/file.bin");
var result = await r2.DownloadFileAsync(
bucketName: "my-bucket",
objectKey: "data/file.bin",
outputStream: fileStream);
To Memory
await using var memoryStream = new MemoryStream();
var result = await r2.DownloadFileAsync(
bucketName: "my-bucket",
objectKey: "config/settings.json",
outputStream: memoryStream);
memoryStream.Position = 0;
var content = Encoding.UTF8.GetString(memoryStream.ToArray());
To HTTP Response (ASP.NET)
[HttpGet("files/{key}")]
public async Task<IActionResult> DownloadFile(string key)
{
var stream = new MemoryStream();
await r2.DownloadFileAsync("uploads", key, stream);
stream.Position = 0;
return File(stream, "application/octet-stream", Path.GetFileName(key));
}
R2Result
Download operations return metrics:
var result = await r2.DownloadFileAsync("bucket", "key", downloadPath);
Console.WriteLine($"Class A Operations: {result.ClassAOperations}"); // Always 0
Console.WriteLine($"Class B Operations: {result.ClassBOperations}"); // 1 for GET
Console.WriteLine($"Ingress Bytes: {result.IngressBytes}"); // Always 0
Console.WriteLine($"Egress Bytes: {result.EgressBytes}"); // Downloaded size
Error Handling
try
{
await r2.DownloadFileAsync("bucket", "key", localPath);
}
catch (CloudflareR2OperationException ex)
{
if (ex.Message.Contains("NoSuchKey"))
{
Console.WriteLine("Object not found");
}
else
{
Console.WriteLine($"Download failed: {ex.Message}");
}
}
catch (IOException ex)
{
Console.WriteLine($"Local file error: {ex.Message}");
}
Common Patterns
Download with Progress
public async Task DownloadWithProgressAsync(
string bucket, string key, string localPath, IProgress<long> progress)
{
// Create a progress-tracking stream wrapper
await using var fileStream = File.Create(localPath);
await using var progressStream = new ProgressStream(fileStream, progress);
await r2.DownloadFileAsync(bucket, key, progressStream);
}
// Simple progress stream
public class ProgressStream(Stream inner, IProgress<long> progress) : Stream
{
private long _bytesWritten;
public override void Write(byte[] buffer, int offset, int count)
{
inner.Write(buffer, offset, count);
_bytesWritten += count;
progress.Report(_bytesWritten);
}
// Implement other required Stream members...
}
Download Multiple Files
public async Task<R2Result> DownloadAllAsync(
string bucket, string prefix, string localDir)
{
var total = new R2Result();
// First, list objects
var listResult = await r2.ListObjectsAsync(bucket, prefix);
total += listResult.Metrics;
// Download each
foreach (var obj in listResult.Data)
{
var relativePath = obj.Key.Substring(prefix.Length).TrimStart('/');
var localPath = Path.Combine(localDir, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(localPath)!);
var result = await r2.DownloadFileAsync(bucket, obj.Key, localPath);
total += result;
Console.WriteLine($"Downloaded: {obj.Key}");
}
return total;
}
Download with Retry
public async Task<R2Result> DownloadWithRetryAsync(
string bucket, string key, string localPath, int maxRetries = 3)
{
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
return await r2.DownloadFileAsync(bucket, key, localPath);
}
catch (CloudflareR2OperationException) when (attempt < maxRetries)
{
Console.WriteLine($"Attempt {attempt} failed, retrying...");
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
}
}
throw new InvalidOperationException($"Download failed after {maxRetries} attempts");
}
Download and Deserialize JSON
public async Task<T?> DownloadJsonAsync<T>(string bucket, string key)
{
await using var stream = new MemoryStream();
await r2.DownloadFileAsync(bucket, key, stream);
stream.Position = 0;
return await JsonSerializer.DeserializeAsync<T>(stream);
}
Check If Object Exists Before Download
public async Task<bool> TryDownloadAsync(
string bucket, string key, string localPath)
{
try
{
await r2.DownloadFileAsync(bucket, key, localPath);
return true;
}
catch (CloudflareR2OperationException ex) when (ex.Message.Contains("NoSuchKey"))
{
return false;
}
}
R2 Pricing Note
R2 has free egress - downloading files doesn't incur bandwidth charges. You only pay for Class B operations ($0.36 per million GET requests).
Related
- Uploading Objects - Upload files to R2
- Listing Objects - Find objects to download
- Deleting Objects - Remove downloaded objects