In this article, we will learn how to:
- Upload images from a .NET backend to Cloudflare R2
- Generate a public image URL
- Store the image URL in a database
- Return the image URL to mobile or web applications
By the end of this guide, your application architecture will look like this:
Mobile App / Web App ↓ .NET API ↓ Cloudflare R2 ↓ Public Image URL ↓ Database
Why Use Cloudflare R2?
Cloudflare R2 offers several advantages:
- S3-compatible API
- Lower storage costs
- No egress fees in many scenarios
- Global CDN integration
- High scalability
- Easy integration with .NET applications
Instead of storing images on your hosting server, you can store them in R2 and save only the image URL in your database.
Step 1: Create an R2 Bucket
Login to the Cloudflare Dashboard.
Navigate to: R2 Object Storage Create Bucket Example: Bucket Name: food-images
After creating the bucket, enable Public Access. Cloudflare will provide a public URL similar to: https://pub-xxxxxxxxxxxxxxxx.r2.dev
Keep this URL because it will be used to access uploaded images.
Step 2: Create an R2 API Token
Navigate to: R2 → Manage R2 API Tokens Create API Token Select: Object Read & Write
Cloudflare will generate:
- Access Key ID
- Secret Access Key
- Endpoint URL
Example: Access Key: abc123
Secret Key: xyz456
Endpoint: https://123456789.r2.cloudflarestorage.com
Store these credentials securely.
Step 3: Install AWS S3 SDK
Cloudflare R2 is fully compatible with the AWS S3 SDK.
Install the package: dotnet add package AWSSDK.S3
Step 4: Configure Application Settings
Add Cloudflare configuration to appsettings.json. { "CloudflareR2": { "AccessKey": "YOUR_ACCESS_KEY", "SecretKey": "YOUR_SECRET_KEY", "ServiceUrl": "https://123456789.r2.cloudflarestorage.com", "BucketName": "food-images", "PublicUrl": "https://pub-xxxxxxxxxxxxxxxx.r2.dev" } }
Step 5: Create Configuration Model
Create a settings class.
public class CloudflareR2Settings
{
public string AccessKey { get; set; }
public string SecretKey { get; set; }
public string ServiceUrl { get; set; }
public string BucketName { get; set; }
public string PublicUrl { get; set; }
}
Register the settings.
builder.Services.Configure<CloudflareR2Settings>(
builder.Configuration.GetSection("CloudflareR2"));
Step 6: Create the Upload Service
Create a service responsible for uploading files to Cloudflare R2.
using Amazon.S3;
using Amazon.S3.Model;
using Microsoft.Extensions.Options;
public class R2StorageService
{
private readonly CloudflareR2Settings _settings;
private readonly AmazonS3Client _client;
public R2StorageService(
IOptions<CloudflareR2Settings> options)
{
_settings = options.Value;
var config = new AmazonS3Config
{
ServiceURL = _settings.ServiceUrl,
ForcePathStyle = true
};
_client = new AmazonS3Client(
_settings.AccessKey,
_settings.SecretKey,
config);
}
public async Task<string> UploadImageAsync(IFormFile file)
{
var extension = Path.GetExtension(file.FileName);
var fileName =
$"{Guid.NewGuid()}{extension}";
using var stream = file.OpenReadStream();
var request = new PutObjectRequest
{
BucketName = _settings.BucketName,
Key = fileName,
InputStream = stream,
ContentType = file.ContentType
};
await _client.PutObjectAsync(request);
return $"{_settings.PublicUrl}/{fileName}";
}
}
Register the service:
builder.Services.AddScoped<R2StorageService>();
Step 7: Create an Upload API Endpoint
Create an API controller.
[ApiController]
[Route("api/images")]
public class ImagesController : ControllerBase
{
private readonly R2StorageService _storage;
public ImagesController(R2StorageService storage)
{
_storage = storage;
}
[HttpPost]
public async Task<IActionResult> Upload(
IFormFile image)
{
var imageUrl =
await _storage.UploadImageAsync(image);
return Ok(new
{
Url = imageUrl
});
}
}
This endpoint accepts an image file, uploads it to R2, and returns the public URL.
Step 8: Test the Upload
Open Postman.
Send:
POST /api/images
Body:
form-data
Key:
image
Type:
File
Select an image and send the request.
Example response:
{
"url": "https://pub-xxxxxxxxxxxxxxxx.r2.dev/12345.jpg"
}
Open the URL in a browser to verify the upload.
Step 9: Save the URL to the Database
Instead of saving the image itself, save the image URL.
Example entity:
public class FoodLog
{
public int Id { get; set; }
public string ImageUrl { get; set; }
public DateTime CreatedAt { get; set; }
}
Save after upload:
var imageUrl =
await _storage.UploadImageAsync(image);
var foodLog = new FoodLog
{
ImageUrl = imageUrl,
CreatedAt = DateTime.UtcNow
};
_context.FoodLogs.Add(foodLog);
await _context.SaveChangesAsync();
Database record:
https://pub-xxxxxxxxxxxxxxxx.r2.dev/12345.jpg
Step 10: Display Images in the Mobile App
The backend returns:
{
"url": "https://pub-xxxxxxxxxxxxxxxx.r2.dev/12345.jpg"
}
In SwiftUI:
AsyncImage(
url: URL(string: imageUrl)
)
The image will be loaded directly from Cloudflare R2.
Organizing Uploaded Files
As your application grows, avoid storing all files in the bucket root.
Instead:
food-images/
├── foods/
├── profiles/
├── receipts/
└── invoices/
Example:
var fileName =
$"foods/{Guid.NewGuid()}.jpg";
Generated URL:
https://pub-xxxxxxxxxxxxxxxx.r2.dev/foods/abc123.jpg
This keeps storage organized and easier to maintain.
Conclusion
Using Cloudflare R2 with a .NET backend provides a secure and scalable solution for image storage. The recommended workflow is:
- User uploads an image from the mobile app.
- The .NET API receives the file.
- The API uploads the file to Cloudflare R2.
- Cloudflare returns a public image URL.
- The URL is stored in the database.
- The mobile application displays the image using the stored URL.
This approach reduces server storage usage, improves scalability, and makes image delivery faster through Cloudflare's infrastructure.
0 Comments
Leave a Comment