React向Asp.Net Core后端多文件(图片)上传失败求助
React多图片上传至ASP.NET Core后端400错误解决方法
问题描述
React前端多图片上传至ASP.NET Core后端时出现400错误,后端无法识别图片集合;但单文件上传功能正常。前端通过Blob格式发送图片,后端使用MediatR处理请求。
相关代码
后端代码
Command实体类
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; } // 多文件接收属性(原问题中无法工作) // public IFormFile Photo { get; set; } // 单文件接收属性(可正常工作) }
控制器代码
[HttpPost] public async Task<ActionResult<ProductDTO>> CreateProduct([FromForm] CreateProduct.Command command) { return await _mediator.Send(command); }
前端代码
IPhoto接口
export interface IPhoto { lastModified: number; lastModifiedDate: Date; name: string; path: string; preview: string; size: number; type: string; webkitRelativePath: string; }
DropZone组件
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;
原提交逻辑(错误版本)
const [files, setFiles] = useState<any[]>([]); const onSubmit = async (data: Omit<ProductValues, 'photos'>) => { const photos = files.map((file) => new Blob([file], { type: 'image / png' })); // 错误:将File转为Blob,且未正确构造FormData // const photo = new Blob([files[0]], { type: 'image / png' }); // 单文件时可工作 dispatch( productCreateProductActions.request({ product: { ...data, photos: photos, }, }) ); };
原API请求代码(错误版本)
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}`), };
错误原因
- FormData构造错误:直接将包含文件数组的普通对象传给axios,即使设置了
multipart/form-data,axios无法自动将数组转换为符合多文件上传要求的FormData格式,后端无法解析。 - 多余的Blob转换:Dropzone获取的
file本身就是File对象(Blob的子类),手动转为Blob完全多余,还可能破坏文件结构。 - Content-Type设置错误:手动设置
multipart/form-data时,axios不会自动生成boundary分隔符,导致后端无法正确解析请求体。
解决方案
1. 修改前端提交逻辑,手动构造FormData
const [files, setFiles] = useState<File[]>([]); // 直接使用File类型,如需预览可保留扩展属性 const onSubmit = async (data: Omit<ProductValues, 'photos'>) => { const formData = new FormData(); // 添加普通文本字段 formData.append('Title', data.Title); formData.append('Description', data.Description); formData.append('Price', data.Price.toString()); // 添加多文件,键名必须与后端Command的`Photos`属性完全一致 files.forEach(file => { formData.append('Photos', file); }); dispatch( productCreateProductActions.request({ product: formData, // 传递FormData而非普通对象 }) ); };
2. 修改API请求代码,移除手动Content-Type设置
export const api = { // 其他接口不变 createProduct: (formData: FormData): Promise<ExtendedAxiosResponse> => apiClient.post('/products', formData) // 让axios自动处理Content-Type,会自动添加正确的boundary };
3. 调整Saga中的参数传递
function* createProduct({ payload }: AppAction<CreateProductActions>) { const resp: ExtendedAxiosResponse = yield call(api.createProduct, payload.product); // 后续逻辑保持不变 }
4. 后端代码无需修改
后端的Command类和控制器代码已经正确配置,[FromForm]会自动解析FormData中的多文件到IEnumerable<IFormFile> Photos属性。
验证说明
修改后,前端发送的请求体将以正确的multipart/form-data格式传递,多文件会以相同的Photos键名多次出现,后端能正确绑定到IEnumerable<IFormFile>集合,解决400错误。
内容的提问来源于stack exchange,提问作者Blablabla
相关产品推荐
相关产品推荐

