Home > Enterprise >  Asp.Net core React uploading multiple files
Asp.Net core React uploading multiple files

Time:08-29

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: enter image description here

Error I receive after trying to upload multiple files: enter image description here

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',
   }
)
  • Related