Export files to DigitalOcean Spaces in .NET (C#)
Use the open-source AWS SDK to export local files to an existing DigitalOcean Space. This tutorial provides one complete .NET 10 program for files up to 64 MiB.
Understand DigitalOcean Spaces S3 compatibility
Spaces supports a subset of the S3 API. Configure the regional endpoint and signing region explicitly. Both path-style and virtual-hosted requests are supported; this example uses path style.
Install required NuGet packages
Install the .NET 10 SDK, then run:
dotnet new console --framework net10.0 --name SpacesExport
cd SpacesExport
dotnet add package AWSSDK.Core --version 4.0.102.6
dotnet add package AWSSDK.S3 --version 4.0.103.3
Use AWS SDK for .NET with DigitalOcean Spaces
Set the following environment variables through your shell or secret manager:
| Variable | Value |
|---|---|
SPACES_ENDPOINT | Regional endpoint, such as https://nyc3.digitaloceanspaces.com |
SPACES_REGION | Matching region, such as nyc3 |
SPACES_BUCKET | Existing Space name |
SPACES_KEY_ID | Spaces access key |
SPACES_APPLICATION_KEY | Spaces secret key |
Replace Program.cs with the following. DigitalOcean’s
SDK guide describes the endpoint
configuration. Supply object keys as plain strings, including spaces or Unicode; the SDK handles
URL encoding and Signature V4.
using Amazon.Runtime;
using Amazon.S3;
using Amazon.S3.Model;
public static class SpacesExporter
{
public static async Task<int> Main(string[] args)
{
using var deadline = new CancellationTokenSource(TimeSpan.FromMinutes(5));
Console.CancelKeyPress += (_, e) => { e.Cancel = true; deadline.Cancel(); };
try
{
if (args.Length != 2) throw new ArgumentException("Pass a local file and an object key.");
var endpoint = new Uri(Required("SPACES_ENDPOINT"));
if (endpoint.Scheme != "https" || endpoint.UserInfo.Length != 0 ||
endpoint.AbsolutePath != "/" || endpoint.Query.Length != 0 || endpoint.Fragment.Length != 0)
throw new ArgumentException("Use the HTTPS regional endpoint without a bucket or path.");
var config = new AmazonS3Config
{
ServiceURL = endpoint.AbsoluteUri,
AuthenticationRegion = Required("SPACES_REGION"),
ForcePathStyle = true,
MaxErrorRetry = 2,
RequestChecksumCalculation = RequestChecksumCalculation.WHEN_REQUIRED,
ResponseChecksumValidation = ResponseChecksumValidation.WHEN_REQUIRED
};
using var client = new AmazonS3Client(
Required("SPACES_KEY_ID"), Required("SPACES_APPLICATION_KEY"), config);
await UploadAsync(client, Required("SPACES_BUCKET"), args[0], args[1], deadline.Token);
Console.WriteLine("Uploaded.");
return 0;
}
catch (OperationCanceledException) { Console.Error.WriteLine("Upload canceled or timed out."); }
catch (AmazonS3Exception error) { Console.Error.WriteLine($"Storage returned HTTP {(int)error.StatusCode}."); }
catch (Exception) { Console.Error.WriteLine("Upload failed. Check the file and storage configuration."); }
return 1;
}
private static string Required(string name) =>
Environment.GetEnvironmentVariable(name) is { Length: > 0 } value
? value : throw new ArgumentException($"Missing {name}.");
public static async Task UploadAsync(IAmazonS3 client, string bucket, string path,
string key, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(key)) throw new ArgumentException("An object key is required.");
await using var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
if (input.Length > 64L * 1024 * 1024) throw new IOException("This example accepts at most 64 MiB.");
await client.PutObjectAsync(new PutObjectRequest
{
BucketName = bucket,
Key = key,
InputStream = input,
AutoCloseStream = false,
ContentType = "application/octet-stream",
UseChunkEncoding = false
}, cancellationToken);
}
}
Create an input file and start the program from the project directory:
printf 'example export\n' > report.txt
dotnet run -- report.txt 'reports/report #1.txt'
Add retry logic and error handling
The SDK performs up to two retries for retryable failures. The five-minute deadline and Ctrl+C cancel both retries and the upload. Permanent authorization failures exit nonzero without exposing credentials or service response bodies. Check the remote object after an uncertain network failure before retrying an operation whose replacement behavior matters.
Optimize performance with multipart uploads and streaming
The program streams one PUT from an open file, with a 64 MiB application limit. It does not perform
multipart uploads. Keep the file unchanged while uploading. For larger files, use the AWS SDK’s
TransferUtility and arrange cleanup for incomplete multipart uploads; see the
AWS transfer documentation.
The file stream and client are disposed even when a request fails. Reuse a client across uploads in a long-running application, and bound concurrent operations to fit its memory and network budget.
Manage access keys and permissions securely
Use a Spaces key scoped to the destination bucket. The example supplies credentials explicitly, uses HTTPS, and leaves the object private by default. It does not change bucket policy or ACLs.
Compare the approaches
The AWS SDK handles signing, object-key encoding, response errors, and retries in one dependency.
A storage abstraction is useful when an application already relies on several providers. Direct
HttpClient integration requires a complete Signature V4 implementation; the SDK avoids maintaining
that implementation in application code.
Browser uploads are a separate flow: issue a short-lived presigned URL from your backend and configure the Space’s CORS policy for the browser origin. Keep the secret key on the server.
Troubleshoot common issues
For HTTP 403, check the access key, secret, bucket permissions, endpoint, and signing region.
For a missing bucket, create the Space first or correct SPACES_BUCKET. If a file exceeds this
example’s limit, switch to a deliberate multipart workflow rather than removing the bound.
Transloadit’s DigitalOcean export Robot can also write the output of a processing pipeline to your Space.
