Last updated: May 29, 2025

<span aria-hidden="true" id="export-files-to-digitalocean-spaces-in-net-c"></span>

# Export files to DigitalOcean Spaces in .Net (C#)

![Tim Koschützki](/assets/images/teammates/avatar-tim-kos-1.jpg?dpl=dpl_5eQDcFwLGbzg3PfkSEW2a2j2x5pY)

**Tim Koschützki**

Co-founder · Berlin, Germany · Show bio

[](https://x.com/tim%5Fkos)[](https://github.com/tim-kos)

DigitalOcean Spaces offers a cost-effective, S3-compatible object storage service, so you can reach for the same tooling you already use for Amazon S3. In this guide, you will learn three modern ways to export files to DigitalOcean Spaces with .NET (C#) 8: using the official AWS SDK, the polycloud-friendly FluentStorage library, and plain `HttpClient` for a zero-dependency approach.

<span aria-hidden="true" id="understand-digitalocean-spaces-s3-compatibility"></span>

## Understand DigitalOcean Spaces S3 compatibility

Because Spaces speaks the S3 protocol, every library that works with Amazon S3 also works with DigitalOcean. The only real change is the endpoint—`https://<region>.digitaloceanspaces.com`—and a few minor configuration flags.

<span aria-hidden="true" id="install-required-nuget-packages"></span>

## Install required nuget packages

```xml
<!-- AWS SDK for S3-compatible storage -->
<PackageReference Include="AWSSDK.S3" Version="3.7.300.1" />

<!-- Resilience & retry policies -->
<PackageReference Include="Polly" Version="8.5.2" />

<!-- Polycloud abstraction layer -->
<PackageReference Include="FluentStorage" Version="5.6.0" />

```

<span aria-hidden="true" id="use-aws-sdk-for-net-with-digitalocean-spaces"></span>

## Use AWS SDK for .Net with DigitalOcean Spaces

The AWS SDK is feature-rich and automatically handles multipart uploads, encryption headers, and other niceties.

```csharp
using Amazon;
using Amazon.S3;
using Amazon.S3.Transfer;

var config = new AmazonS3Config
{
    ServiceURL = "https://nyc3.digitaloceanspaces.com", // change region as needed
    ForcePathStyle = false, // Required for DigitalOcean Spaces
};

await using var client = new AmazonS3Client(
    Environment.GetEnvironmentVariable("DO_SPACES_ACCESS_KEY"),
    Environment.GetEnvironmentVariable("DO_SPACES_SECRET_KEY"),
    config);

var transfer = new TransferUtility(client);
await transfer.UploadAsync("./local/file.txt", "my-space", "file.txt");
Console.WriteLine("✔ Uploaded with AWS SDK");

```

<span aria-hidden="true" id="asyncawait-best-practices"></span>

### Async/await best practices

Every network call returns a `Task`, so `await` each one to avoid blocking threads. Wrapping upload operations in `Task.Run` is unnecessary and actually degrades throughput.

<span aria-hidden="true" id="leverage-fluentstorage-for-a-polycloud-approach"></span>

## Leverage fluentstorage for a polycloud approach

FluentStorage abstracts multiple providers behind a single API, making it straightforward to swap between Spaces, S3, and others.

```csharp
using FluentStorage;
using FluentStorage.AWS;

var blobs = StorageFactory.Blobs.AwsS3(
    accessKey: Environment.GetEnvironmentVariable("DO_SPACES_ACCESS_KEY"),
    secretKey: Environment.GetEnvironmentVariable("DO_SPACES_SECRET_KEY"),
    serviceUrl: "nyc3.digitaloceanspaces.com",
    bucketName: "my-space",
    options: new AwsS3Options { UseHttp = false }); // UseHttp = false for HTTPS

await using var stream = File.OpenRead("./local/file.txt");
await blobs.WriteAsync("file.txt", stream);
Console.WriteLine("✔ Uploaded with FluentStorage");

```

<span aria-hidden="true" id="call-the-rest-api-directly-with-httpclient"></span>

## Call the REST API directly with HttpClient

When you need complete control—or want to ship a single-file tool—you can sign your own requests with AWS Signature V4 and send them via `HttpClient`.

```csharp
public sealed class SpacesClient
{
    private readonly HttpClient _http = new();
    private readonly string _key = Environment.GetEnvironmentVariable("DO_SPACES_ACCESS_KEY")!;
    private readonly string _secret = Environment.GetEnvironmentVariable("DO_SPACES_SECRET_KEY")!;
    private readonly string _region;
    private readonly string _space;

    public SpacesClient(string region, string space)
    {
        _region = region;
        _space = space;
    }

    public async Task UploadAsync(string path, string objectKey)
    {
        await using var fs = File.OpenRead(path);
        using var content = new StreamContent(fs);
        content.Headers.ContentType = new("application/octet-stream");

        var url = $"https://{_space}.{_region}.digitaloceanspaces.com/{objectKey}";
        using var req = new HttpRequestMessage(HttpMethod.Put, url) { Content = content };

        SignRequest(req); // left-out for brevity—see AWS SigV4 docs
        var res = await _http.SendAsync(req);
        res.EnsureSuccessStatusCode();
        Console.WriteLine("✔ Uploaded with HttpClient");
    }
}

```

The SigV4 signing routine is about 40 lines and available in the AWS docs. Copy that in, or better yet, reference `AWSSDK.S3` even when using `HttpClient` so you can reuse its `AWSSDKUtils` helper.

<span aria-hidden="true" id="add-retry-logic-and-error-handling"></span>

## Add retry logic and error handling

Transient errors like `503 Slow Down` are common with large uploads. Polly v8 makes retries concise:

```csharp
using Polly;
using Amazon.S3; // Required for AmazonS3Exception
using Amazon.S3.Transfer; // Required for TransferUtility, assuming 'transfer' is an instance from AWS SDK section

// Assuming 'transfer' is an instance of TransferUtility initialized in the AWS SDK section.

var retryPolicy = new ResiliencePipelineBuilder()
    .AddRetry(new RetryStrategyOptions
    {
        MaxRetryAttempts = 3,
        BackoffType = DelayBackoffType.Exponential,
        Delay = TimeSpan.FromSeconds(2),
        UseJitter = true
    })
    .Build();

try
{
    await retryPolicy.ExecuteAsync(async token => // The cancellation token is optional if not used directly in your delegate
    {
        await transfer.UploadAsync("./local/file.txt", "my-space", "file.txt");
    });
    Console.WriteLine("✔ Uploaded with Polly retry logic");
}
catch (AmazonS3Exception ex) when (ex.ErrorCode == "NoSuchBucket")
{
    Console.WriteLine($"Error: The bucket 'my-space' does not exist. {ex.Message}");
    // Handle missing bucket, e.g., log the error or inform the user
}
catch (AmazonS3Exception ex) when (ex.ErrorCode == "AccessDenied")
{
    Console.WriteLine($"Error: Access denied. Check your credentials and permissions. {ex.Message}");
    // Handle authentication errors, e.g., prompt for new credentials or log
}
catch (Exception ex) // Catch other potential exceptions during the upload
{
    Console.WriteLine($"An unexpected error occurred during upload: {ex.Message}");
    // Handle other exceptions
}

```

<span aria-hidden="true" id="optimize-performance-with-multipart-uploads-and-streaming"></span>

## Optimize performance with multipart uploads and streaming

The AWS SDK automatically switches to multipart uploads for files larger than 16 MiB. You can tune this threshold and concurrency:

```csharp
var transfer = new TransferUtility(client, new TransferUtilityConfig
{
    MinSizeBeforePartUpload = 16 * 1024 * 1024, // 16 MiB
    ConcurrentServiceRequests = 10,
});

```

<span aria-hidden="true" id="manage-access-keys-and-permissions-securely"></span>

## Manage access keys and permissions securely

1. Generate a dedicated Spaces key with the minimum permissions your app needs.
2. Store the key as environment variables—never hard-code credentials.
3. Rotate keys periodically and immediately if you suspect a leak.

```csharp
var key = Environment.GetEnvironmentVariable("DO_SPACES_ACCESS_KEY")
          ?? throw new InvalidOperationException("Missing DO_SPACES_ACCESS_KEY credential");
var secret = Environment.GetEnvironmentVariable("DO_SPACES_SECRET_KEY")
          ?? throw new InvalidOperationException("Missing DO_SPACES_SECRET_KEY credential");

```

<span aria-hidden="true" id="configure-cors-for-browser-uploads"></span>

## Configure CORS for browser uploads

If your front-end talks directly to Spaces, add a CORS rule in the DigitalOcean dashboard:

```xml
<CORSConfiguration>
  <CORSRule>
    <AllowedOrigin>https://example.com</AllowedOrigin>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
  </CORSRule>
</CORSConfiguration>

```

<span aria-hidden="true" id="compare-the-approaches"></span>

## Compare the approaches

|Scenario|Best Fit|
|-|-|
|Feature-rich uploads, server-side apps|AWS SDK|
|One codebase, many providers|FluentStorage|
|Single-file utility, no external deps|Raw HttpClient|

<span aria-hidden="true" id="troubleshoot-common-issues"></span>

## Troubleshoot common issues

* **403 Forbidden** → Check access keys, permissions, and make sure the Space (bucket) exists and the name is correct.
* **NoSuchBucket** → Bucket name (Space name) is case-sensitive and must exist.
* **CORS errors in browser** → Review the CORS rule above and ensure `AllowedOrigin` matches your application's domain exactly. Check for typos.
* **Slow uploads** → Increase `ConcurrentServiceRequests` in the AWS SDK `TransferUtilityConfig` or switch to a DigitalOcean Spaces region closer to your users or application server.
* **`InvalidOperationException: Missing DO_SPACES_ACCESS_KEY credential` or`Missing DO_SPACES_SECRET_KEY credential`** → Ensure `DO_SPACES_ACCESS_KEY` and`DO_SPACES_SECRET_KEY` environment variables are set correctly where your application is running.

Integrating DigitalOcean Spaces into .NET applications is quick and painless—choose the library that aligns with your project’s architecture and get shipping.

At Transloadit, we also offer a hassle-free way to export files to Spaces with our[🤖 /digitalocean/store](/docs/robots/digitalocean-store.md) Robot, part of our[File Exporting service](/services/file-exporting.md). The Robot accepts these key parameters:

* `credentials` – Template Credentials pointing to your Spaces access key and secret.
* `space` – the Space (bucket) name.
* `region` – Spaces region, such as `nyc3` or `ams3`.
* `path` – an upload path template. Default: `${unique_prefix}/${file.url_name}`.
* `acl` – object ACL, defaulting to `public-read`.

Give it a try for effortless, reliable exports straight from your encoding pipeline.

\#digitalocean-spaces#dotnet#csharp#aws-sdk#fluentstorage#rest-api#httpclient#multipart-upload#cloud-storage#file-exporting-service

### 👩‍💻 Join 20k+ developers

Sign up for our [monthly newsletter](/newsletters.md) to receive direct links to 3 exclusive tech — and 2 product updates. No less, no more.

Your email:

Get access

## File uploading and encoding. Made simple.

Transloadit streamlines file handling for developers, trusted by brands like Coursera and The New York Times. We’re known for a reliable API, top-notch support, and a strong commitment to open source, with projects like [Uppy⁠](https://uppy.io) and [Tus⁠](https://tus.io) setting standards in file processing.

[Sign up](/c/)[Book a Demo](https://survey.typeform.com/to/kRg47Xi5)

No credit card needed

Cancel anytime
