My project is setup as follows:
Frontend - TypeScript, React, fetch()
for calling my backend API as opposed to axios.
Backend - C#, ASP .NET Core, Swagger UI, Azure
I am trying to implement a simple upload feature of image files to my S3 bucket via my own API backend deployed on Azure. When I test just the API endpoint for uploading with Swagger UI, it works without any issues. However, when I try calling the same endpoint from my frontend with fetch()
I get the following error:
ImageUploadFunction.tsx:33 POST https://project.azurewebsites.net/api/files/upload 500 (Internal Server Error)
Backend code: FilesController.cs
using System.Threading.Tasks;
using Amazon.S3;
using Amazon.S3.Model;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System;
using System.Linq;
using API.Model;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
namespace API.Controllers
{
[Route("api/files")]
[ApiController]
public class FilesController : BaseApiController
{
private readonly IAmazonS3 _s3Client;
private readonly IOptions<AWSSecretsModel> awsSettings;
private readonly IConfiguration _config;
public FilesController(IAmazonS3 s3Client, IOptions<AWSSecretsModel> aws, IConfiguration config)
{
_s3Client = s3Client;
awsSettings = aws;
_config = config;
}
[HttpPost("upload")]
public async Task<IActionResult> UploadFileAsync(IFormFile file)
{
if (file is null)
{
throw new ArgumentNullException(nameof(file));
}
try
{
var awsBucketName = awsSettings.Value.AWS_S3_BUCKET;
var awsRegion = awsSettings.Value.AWS_REGION;
// Upload the raw image to S3
var bucketExists = await _s3Client.DoesS3BucketExistAsync(awsBucketName);
if (!bucketExists) return NotFound($"Bucket {awsBucketName} does not exist.");
var fileNameToPrefix = file.FileName;
var request = new PutObjectRequest
{
BucketName = awsBucketName,
Key = fileNameToPrefix,
InputStream = file.OpenReadStream()
};
await _s3Client.PutObjectAsync(request);
return Ok($"File {file.FileName} [{fileNameToPrefix}] uploaded to S3 successfully!");
}
catch (ArgumentNullException ex)
{
return BadRequest(ex.Message);
}
catch (AmazonS3Exception ex)
{
return BadRequest($"Error uploading to S3: {ex.Message}");
}
catch (Exception ex)
{
return BadRequest($"Error: {ex.Message}");
}
}
}
}
Frontend: ImageUploadFunction.tsx
import { useState, useEffect, ChangeEvent } from "react";
const API_URL = process.env.REACT_APP_API_URL;
const UPLOAD_URL = API_URL + `/api/files/upload`;
const ImageUpload: React.FC = () => {
const [selectedImage, setSelectedImage] = useState<File | null>(null);
const [uploading, setUploading] = useState(false);
const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (files && files.length > 0) {
setSelectedImage(files[0]);
}
};
const handleImageUpload = async () => {
if (!selectedImage) return;
try {
setUploading(true);
const formData = new FormData();
formData.append('file', selectedImage);
const response = await fetch(UPLOAD_URL, {
method: 'POST',
body: formData,
});
if (response.ok) {
console.log('Image uploaded successfully!');
} else {
console.error('Image upload failed!');
}
} catch (error) {
console.error('Error uploading image:', error);
} finally {
setUploading(false);
}
};
return (
<div>
<input type="file" onChange={handleFileChange} />
<button onClick={handleImageUpload} disabled={!selectedImage || uploading}>
{uploading ? 'Uploading...' : 'Upload Image'}
</button>
</div>
);
};
export default ImageUpload;
Since everything works fine via Swagger and Postman, I feel like the problem has something to do with my frontend, despite the error message. I just can't see what I'm doing wrong.
Any suggestions?
EDIT:
I just noticed that I am only able successfully upload an image file through localhost when I run my backend in its development environment (https://localhost:5001/api/files/upload). Trying to do a POST call via my live app URL (https://project.azurewebsites.net/api/files/upload) gives me the 500 internal server error. So my issue definitely lies in my Azure configuration, right?
EDIT 2:
Below is my bucket policy as well as my CORS configuration for my S3 Bucket.
Bucket policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicRead",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject",
"s3:GetObjectVersion",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::mys3bucketname/*"
}
]
}
CORS configuration:
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"GET",
"PUT",
"POST",
"HEAD"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": []
}
]
EDIT 3: Added exception handling in backend code (FilesController.cs) as suggested in comments.