I need to upload multiple files (photos) from my frontend written in React, to backend (Asp.NET Core). When I allow to upload single file, everything works well, but if I try upload multiple files (collection of photos - files), then I've got 400 error and photos are not recognized. Photos sent to backend are in Blob format. I'm using MediatR on backend.
Endpoint body:
public class Command : IRequest<ProductDTO>
{
public string Title { get; set; }
public string Description { get; set; }
public double Price { get; set; }
public IEnumerable<IFormFile> Photos { get; set; } // this doesn't work
//public IFormFile Photo { get; set; } // this works
}
Controller:
[HttpPost]
public async Task<ActionResult<ProductDTO>> CreateProduct([FromForm] CreateProduct.Command command)
{
return await _mediator.Send(command);
}
Request body when uploading multiple photos:
Error I receive after trying to upload multiple files:
IPhoto interface:
export interface IPhoto {
lastModified: number;
lastModifiedDate: Date;
name: string;
path: string;
preview: string;
size: number;
type: string;
webkitRelativePath: string;
}
React DropZone component where photos are dropped:
import { useCallback } from 'react';
import { useDropzone } from 'react-dropzone';
import { IPhoto } from '../../../models/Photo';
const dropZoneStyles = {
border: 'dashed 3px',
borderColor: '#eee',
borderRadius: '5px',
paddingTop: '30px',
textAlign: 'center' as const,
height: '200px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '0',
};
const dropZoneActive = {
borderColor: 'green',
};
interface Props {
setFiles: (files: IPhoto[]) => void;
}
const Dropzone = ({ setFiles }: Props): JSX.Element => {
const onDrop = useCallback(
(files: any) => {
setFiles(
files.map((file: any) => Object.assign(file, { preview: URL.createObjectURL(file) }))
);
},
[setFiles]
);
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
multiple: true,
accept: {
'image/jpeg': [],
'image/png': [],
},
});
return (
<div
{...getRootProps()}
style={
isDragActive
? {
...dropZoneStyles,
...dropZoneActive,
}
: dropZoneStyles
}
>
<input {...getInputProps()} />
<h4>Drop an Image here</h4>
</div>
);
};
export default Dropzone;
React onSubmit (in files
state are stored images from DropZone component):
const [files, setFiles] = useState<any[]>([]);
const onSubmit = async (data: Omit<ProductValues, 'photos'>) => {
const photos = files.map((file) => new Blob([file], { type: 'image / png' })); // this doesn't work
// const photo = new Blob([files[0]], { type: 'image / png' }); // this works
dispatch(
productCreateProductActions.request({
product: {
...data,
photos: photos,
},
})
);
};
Then this function after dispatch is executed:
function* createProduct({ payload }: AppAction<CreateProductActions>) {
console.log(payload.product.photos);
const resp: ExtendedAxiosResponse = yield call(api.createProduct, payload.product); // this calls api request
if (resp.ok) {
yield put(productCreateProductActions.success(resp.data));
yield put(toastCreateSuccessAction('Product was created.'));
history.push(buildRoute(AppRoutes.Home));
} else {
yield put(productCreateProductActions.failure());
yield put(toastCreateErrorAction('Product creating failed.'));
}
}
api where createProduct
is executed:
import { apiClient, ExtendedAxiosResponse } from '../../helpers/api-client';
import { Product, ProductValues } from '../../models/Product';
export const api = {
getProducts: (): Promise<ExtendedAxiosResponse> => apiClient.get('/products'),
createProduct: (product: ProductValues): Promise<ExtendedAxiosResponse> =>
apiClient.post(
'/products',
{
...product,
},
{
headers: {
'Content-Type': 'multipart/form-data',
},
}
),
deleteProduct: (id: string): Promise<ExtendedAxiosResponse> =>
apiClient.delete(`/products/${id}`),
};
CodePudding user response:
Reqeust body should be like photos[0] = file, photos[1] = file
, for this you should use FormData
.
var formData = new FormData();
formData.append("Title",product.Title)
///other objects
product.photos.forEach((file,i) => formData.append(`Photos[${i}]`,file))
//or product.photos.forEach((file: any, i: number) => formData.append('Photos', file));
apiClient.post(
'/products',
{
formData,
},
{
headers: {
'Content-Type': 'multipart/form-data',
}
)