Implementing S3 Image Uploads with React-Dropzone and .NET
A comprehensive guide to implementing secure image uploads to AWS S3 using React-Dropzone, Next.js, and .NET with Transfer Utility

Implementing Secure S3 Image Uploads with React-Dropzone and .NET#
Building a robust file upload system can be challenging, but with the right tools and architecture, we can create a secure and efficient solution. In this guide, we'll implement an image upload feature using React-Dropzone for the frontend and .NET with AWS Transfer Utility for handling the backend operations with AWS S3.
Frontend Implementation with Next.js and React-Dropzone#
First, let's set up our frontend component using React-Dropzone. We'll create a reusable upload component that handles drag-and-drop functionality and preview capabilities.
// components/ImageUpload.tsx
import { useCallback, useState } from 'react';
import { useDropzone } from 'react-dropzone';
import Image from 'next/image';
interface ImageUploadProps {
onUpload: (file: File) => Promise<void>;
}
export default function ImageUpload({ onUpload }: ImageUploadProps) {
const [preview, setPreview] = useState<string | null>(null);
const [uploading, setUploading] = useState(false);
const [progress, setProgress] = useState(0);
const onDrop = useCallback(async (acceptedFiles: File[]) => {
try {
const file = acceptedFiles[0];
setUploading(true);
setProgress(0);
// Create preview
const objectUrl = URL.createObjectURL(file);
setPreview(objectUrl);
await onUpload(file);
} catch (error) {
console.error('Upload failed:', error);
} finally {
setUploading(false);
}
}, [onUpload]);
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
accept: {
'image/*': ['.jpeg', '.jpg', '.png', '.gif']
},
maxSize: 5242880, // 5MB
multiple: false
});
return (
<div className="w-full max-w-xl mx-auto">
<div
{...getRootProps()}
className={`border-2 border-dashed rounded-lg p-6 text-center cursor-pointer
${isDragActive ? 'border-blue-500 bg-blue-50' : 'border-gray-300'}`}
>
<input {...getInputProps()} />
{uploading ? (
<div>
<p>Uploading... {progress}%</p>
<div className="w-full bg-gray-200 rounded-full h-2.5 mt-2">
<div
className="bg-blue-600 h-2.5 rounded-full"
style={{ width: `${progress}%` }}
></div>
</div>
</div>
) : (
<p>Drag and drop an image here, or click to select</p>
)}
</div>
{preview && (
<div className="mt-4">
<Image
src={preview}
alt="Preview"
width={400}
height={300}
className="rounded-lg"
/>
</div>
)}
</div>
);
}
Backend Implementation with .NET Transfer Utility#
For the backend, we'll create a clean architecture using AWS Transfer Utility for efficient file uploads. First, let's define our S3 service interface:
// Services/Interfaces/IS3Service.cs
public interface IS3Service
{
Task<string> UploadImageAsync(IFormFile file, string prefix = "uploads", IProgress<int> progress = null);
Task DeleteImageAsync(string key);
}
Now, let's implement the S3 service using Transfer Utility:
// Services/S3Service.cs
public class S3Service : IS3Service
{
private readonly IAmazonS3 _s3Client;
private readonly string _bucketName;
private readonly TransferUtility _transferUtility;
public S3Service(
IAmazonS3 s3Client,
IConfiguration configuration)
{
_s3Client = s3Client;
_bucketName = configuration["AWS:BucketName"];
_transferUtility = new TransferUtility(_s3Client);
}
public async Task<string> UploadImageAsync(IFormFile file, string prefix = "uploads", IProgress<int> progress = null)
{
try
{
// Generate unique file name
var fileName = $"{prefix}/{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";
// Create a temporary file
var tempFile = Path.GetTempFileName();
using (var stream = new FileStream(tempFile, FileMode.Create))
{
await file.CopyToAsync(stream);
}
// Configure upload request
var fileTransferUtilityRequest = new TransferUtilityUploadRequest
{
FilePath = tempFile,
BucketName = _bucketName,
Key = fileName,
ContentType = file.ContentType,
CannedACL = S3CannedACL.PublicRead
};
// Add progress tracking
if (progress != null)
{
fileTransferUtilityRequest.UploadProgressEvent += (sender, args) =>
{
var percentDone = (int)((double)args.TransferredBytes * 100 / args.TotalBytes);
progress.Report(percentDone);
};
}
// Upload file
await _transferUtility.UploadAsync(fileTransferUtilityRequest);
// Clean up temporary file
File.Delete(tempFile);
// Return the file URL
return $"https://{_bucketName}.s3.amazonaws.com/{fileName}";
}
catch (Exception ex)
{
throw new ApplicationException("Error uploading to S3", ex);
}
}
public async Task DeleteImageAsync(string key)
{
try
{
await _transferUtility.S3Client.DeleteObjectAsync(_bucketName, key);
}
catch (Exception ex)
{
throw new ApplicationException("Error deleting from S3", ex);
}
}
}
Now let's create our controller to handle the upload requests with progress tracking:
// Controllers/ImageController.cs
[ApiController]
[Route("api/[controller]")]
public class ImageController : ControllerBase
{
private readonly IS3Service _s3Service;
private readonly ILogger<ImageController> _logger;
public ImageController(
IS3Service s3Service,
ILogger<ImageController> logger)
{
_s3Service = s3Service;
_logger = logger;
}
[HttpPost("upload")]
public async Task<IActionResult> UploadImage(IFormFile file)
{
try
{
if (file == null || file.Length == 0)
return BadRequest("No file uploaded");
// Validate file type
var allowedTypes = new[] { "image/jpeg", "image/jpg", "image/png", "image/gif" };
if (!allowedTypes.Contains(file.ContentType.ToLower()))
return BadRequest("Invalid file type");
// Validate file size (5MB max)
if (file.Length > 5242880)
return BadRequest("File size exceeds limit");
var progress = new Progress<int>();
var imageUrl = await _s3Service.UploadImageAsync(file, progress: progress);
return Ok(new { url = imageUrl });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error uploading image");
return StatusCode(500, "Internal server error");
}
}
}
Configuration and Setup#
To complete the implementation, make sure to:
- Install required NuGet packages:
<PackageReference Include="AWSSDK.S3" Version="3.7.300.0" />
<PackageReference Include="AWSSDK.TransferUtility" Version="3.7.300.0" />
- Configure AWS credentials in your
appsettings.json:
{
"AWS": {
"BucketName": "your-bucket-name",
"Region": "your-region"
}
}
- Register services in
Program.cs:
builder.Services.AddAWSService<IAmazonS3>();
builder.Services.AddScoped<IS3Service, S3Service>();
Advantages of Using Transfer Utility#
- Automatic Multipart Uploads: Transfer Utility automatically handles multipart uploads for large files, improving upload reliability and performance.
- Built-in Retry Logic: Provides automatic retry capability for failed uploads.
- Progress Tracking: Native support for upload progress monitoring.
- Pause and Resume: Supports pausing and resuming uploads (useful for large files).
- Concurrent Uploads: Handles multiple file uploads efficiently.
This implementation provides a robust foundation for handling image uploads in your application, with the added benefits of Transfer Utility's reliability and performance features. You can extend it further by implementing pause/resume functionality, adding support for multiple file uploads, or integrating image processing capabilities.
Loading comments…