How to send image with React to AspNetCore - reactjs

I am trying to get a file on my react application like so:
<form onSubmit={this.onSubmit}>
<label htmlFor="Images">Images:</label>
<input type="file" id="Images" onChange={this.onChangeImage} accept="image/png, image/jpeg"/>
<button type="submit" value="SubmitImage" onClick={this.onSubmit}>Submit</button>
</form>
And onSubmit looks like this:
onSubmit = (event) => {
event.preventDefault();
if(event.target.value === "SubmitImage"){
fetch("https://localhost:5001/api/projects/edit/image/" + this.props.projectId + "/add",
{
method: "PUT",
body: JSON.stringify({photo: this.state.Images[0]})
});
}
else{
return;
}
}
And I am trying to receive this on my backend like so:
[HttpPut]
[Route("projects/edit/image/{id}/{toDo}")]
public void EditProjectImage(string id, string toDo, IFormFile photo)
{
if(toDo == "Add")
{
var result = mContext.Projects.Single(project => project.Id.Equals(id));
}
}
(ignore the lack of logic inside the if statement, I am currently just trying to have this function run)
What am I doing wrong? I have tried using [FromBody], using formdata but nothing works.

Use FormData for sending files
var formData = new FormData();
formData.append("photo", this.state.Images[0]);
fetch("https://localhost:5001/api/projects/edit/image/" + this.props.projectId + "/add",
{
method: "PUT",
body: formData
});
And leave IFormFile photo as is. Model binder binds parameters from form data by default which is correct behavior in this case. Or you may add [FromForm] to this parameter which is effectively the same in this case.

Related

How to upload a file using Gin + React?

I want to upload a image file from React, doing so seems I need to store the image on the server and retrieve it from a POST request in order to modify it into a url path.
Using Gin Gonic http framework.
Here is the form in React for my POST request:
<form action="/upload" method="POST">
<label htmlFor="upload" onChange={onChange}>
<FontAwesomeIcon icon={faUserCircle} color="#3B5998" size="10x" className={`${pointer}`} />
<input type="file" hidden id="upload" name="upload" />
</label>
</form>
Here is my method I am calling in an onChange to send the request along with the image file:
uploadFile = (file) => {
const formData = new FormData();
formData.append('image', file);
axios({
method: 'post',
url: `${gamingApiBaseURL}/v1/upload-image`,
data: formData,
headers: { 'Content-Type': 'multipart/form-data' },
}).catch((error) => {
console.log('error storing image', error);
});
};
Here is my Go struct in order to bind the request
type ImageForm struct {
Image *multipart.FileHeader `form:"upload binding:"required"`
}
Lastly, the method that is supposed to get the image file and upload it to the server. Here is what I have so far. I am receiving an error saying http: no such file which is weird because in Postman I see the image key and the filename, I'm trying to make sense of how uploading an image works in Go.
func extractImage(c *gin.Context) {
// Using 'ShouldBind'
formFile, err := c.FormFile("image")
fmt.Println("Error", err)
if err != nil {
c.JSON(http.StatusBadRequest, err)
}
fmt.Println("form error", formFile)
}
I think this struct tag is wrong
type ImageForm struct {
Image *multipart.FileHeader `form:"upload binding:"required"`
}
Change to this (missing double quote after upload):
type ImageForm struct {
Image *multipart.FileHeader `form:"upload" binding:"required"`
}

An error occurs when sending multipart files using formData

I'd like to send a file using "formData".
I'm using Spring boot for backend, and React.js for frontend.
With PostMan, I checked the Spring boot to ensure that the files are stored properly.
But when I attach a file to formData and send it, an error occurs.
Below is the error.
.w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'file' is not present]
Below is a part of React.js
class Test extends Component {
constructor(props){
super(props);
this.state = {
fileList:[]
};
this.uploadNewWebtoon = this.uploadNewWebtoon.bind(this);
this.onChange = this.onChange.bind(this);
}
onChange=({ fileList })=> {
this.setState({ fileList }, function(){
console.log(this.state.fileList[0].originFileObj)
})
}
uploadNewWebtoon() {
try {
uploadFile(this.state.fileList[0].originFileObj)
} catch(error) {
notification.error({
message: 'Cheeze Toon',
description: error.message || 'Try again.'
});
}
}
render() {
return (
<div className="newAdd-container">
<Form onSubmit={this.uploadNewWebtoon}>
<Form.Item label="Thumbnail">
<Dragger onChange={this.onChange} beforeUpload={() => false} >
<p className="ant-upload-drag-icon">
<Icon type="inbox" />
</p>
<p className="ant-upload-text">Click or drag file to this area to upload</p>
<p className="ant-upload-hint">
Support for a single or bulk upload. Strictly prohibit from uploading company data or other
band files
</p>
</Dragger>
</Form.Item>
<Form.Item>
<Button type="primary" className="newAddButton" size="large" htmlType="submit">Save</Button>
</Form.Item>
</Form>
</div>
);
}
}
I'm using the React UI Library called Ant Design. So I followed the official document and used this.state.fileList[0].originFileObj in the same sense as event.target.files[0].
orginFileObj of filesList[0] consists of the following
Below is the part that I create the formData, attach the file to append(), and transfer it to fetch.
import { API_BASE_URL, ACCESS_TOKEN} from '../constants';
const request = (options) => {
const headers = new Headers()
if(localStorage.getItem(ACCESS_TOKEN)) {
headers.append('Authorization', 'Bearer ' + localStorage.getItem(ACCESS_TOKEN))
}
const defaults = {headers: headers};
options = Object.assign({}, defaults, options);
return fetch(options.url, options)
.then(response =>
response.json().then(json => {
if(!response.ok) {
return Promise.reject(json);
}
return json;
})
);
};
export function uploadFile(fileList) {
const formData = new FormData();
formData.append('fileList', fileList);
return request({
url:API_BASE_URL + "/newToonSave",
method: 'POST',
body : formData
})
}
JWT TOKEN is included in the header due to login function.
I tried to attach option after append value, (like fileName = fileList.name...)
but the result was the same.
This is the spring boot controller part.
#RestController
#RequestMapping("/api")
public class ToonController {
private static final Logger logger = LoggerFactory.getLogger(ToonController.class);
#Autowired
private ToonStorageService toonStorageService;
#PostMapping(value = "/newToonSave", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
#PreAuthorize("hasRole('ADMIN')")
public ToonStorage uploadFile(#RequestParam("file") MultipartFile file) {
ToonStorage toonStorage = toonStorageService.storeFile(file);
return toonStorage;
}
}
I don't know why it doesn't work...
I thought Spring boot needed a library related to file upload.
So I added Dependency to pom.xml and added #Bean to Application.java.
However, the same error occurs.
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
#SpringBootApplication
//auto convert
#EntityScan(basePackageClasses = {DemoApplication.class, Jsr310JpaConverters.class})
#EnableConfigurationProperties({
FileStorageProperties.class
})
public class DemoApplication {
#PostConstruct
void init(){
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
return factory.createMultipartConfig();
}
#Bean
public MultipartResolver multipartResolver() {
org.springframework.web.multipart.commons.CommonsMultipartResolver multipartResolver = new org.springframework.web.multipart.commons.CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(512000000);
return multipartResolver;
}
}
I've been struggling for four days. I would really appreciate your help.

react.js file not uploading spring server

In my project, I have Spring Boot in the back-end and React.js in the front.
My back-end is working fine, I know, because I have tested it with Postman.
In the front-end to upload file, I have a named SubmitAssignment, which looks like this:
state={file:''};
uploadFile = (e) =>{
e.preventDefault();
var formData = new FormData();
formData.append("file", this.state.file);
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost:8080/uploadFile");
xhr.onload = function() {
console.log(xhr.responseText);
var response = JSON.parse(xhr.responseText);
if(xhr.status === 200) {
console.log("upload successful");
} else {
console.log("upload failed");
}
};
xhr.send(formData);
};
onInputChange = (e) =>{
this.state.file=e.target.value;
console.log(this.state.file);
};
render() {
return (
<div>
<h1>Please select file(s):</h1>
<form>
<input className="input-file" id="my-file" type="file" onChange={this.onInputChange}/>
<button onClick={this.uploadFile}>Upload</button>
</form>
</div>
);
}
But the problem is upload is failing every time. Maybe the reason is the path, not sure. I tried to console.log the path. And what I got is C:\fakepath\Screenshot (187).png
Now my question if it is because of path, how can I do it correctly(as far as I know browser doesn't allow it for security concern)?
Otherwise, what is the problem? How to solve it ?
The error in browser console :
POST http://localhost:8080/uploadFile 400
And,
{"timestamp":"2019-09-16T07:20:30.382+0000","status":400,"error":"Bad Request","message":"Required request part 'file' is not present","trace":"org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'file' is not present\r\n\tat
.......
Here is the full error message.
If the REST is needed, for any reason :
#PostMapping("/uploadFile")
public UploadFileResponse uploadFile(#RequestParam("file") MultipartFile file) {
String fileName = fileStorageService.storeFile(file);
String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/downloadFile/")
.path(fileName)
.toUriString();
return new UploadFileResponse(fileName, fileDownloadUri,
file.getContentType(), file.getSize());
}
From what I could see, in onInputChange() you are assigning the target value this.state.file=e.target.value; (This has the file path not the actual file)
Instead change to below, Important !
this.state.file=e.target.files[0];
And some suggestions are, use Fetch Api to send post request rather than using Plain old Javascript
const formData = new FormData();
formData.append("file", this.state.file);
fetch('http://localhost:8080/uploadFile', {
method: 'POST',
body: formData
})
.then(success => console.log(success))
.catch(error => console.log(error));
In your Spring boot controller use #RequestPart("file")
#PostMapping("/uploadFile")
public UploadFileResponse uploadFile(#RequestPart("file") MultipartFile file) {
//Logic
}

File upload with react + Vertx

I am working on a web application and i must upload a file the server which is written in Java + VertX
The endpoint is made like this:
private void uploadFromExcel(RoutingContext ctx) {
new ImportFromExcel(ctx.getBody(), ctx.vertx()).startImporting(ev->{
if(ev.failed()){
ctx.fail(ev.cause());
}else {
ctx.response().end();
}
});
}
And the frontend like this:
<input
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
type="file"
onChange={this.onUploadFile}
/>
<label htmlFor="flat-button-file">
<IconButton component="span">
<Icon className={'material icons'}>cloud_upload</Icon>
</IconButton>
</label>
[...]
onUploadFile = (e) =>{
const {reduxApi, setError, setWait, removeWait} = this.context
const { dispatchUploadFile } = this.props
const fileToUpload = e.target.files[0]
dispatchUploadFile(reduxApi, fileToUpload , (err,data)=>{
removeWait()
//e.target.value = null
if(err){
setError(err)
return
}
})
[...]
dispatchUploadFile: (reduxApi, file, cb) =>{
dispatch(reduxApi.actions.cryptoindexUploadExcel.post(null,file,cb))
}
I can upload the file via postman using the Header "Accept-Type":"multipart/form-data". It works fine!
Unfortunatelly I cannot upload the file via react, it throws an error. So I decided to try another solution
let reader = new FileReader()
reader.onload = (event) => {
let arrayBuffer = event.target.result
let array = new Uint8Array(arrayBuffer)
let binaryString = String.fromCharCode.apply(null, array)
console.log(binaryString)
setWait()
dispatchUploadFile(reduxApi, array , (err,data)=>{
removeWait()
if(err){
setError(err)
return
}
})
}
reader.readAsArrayBuffer(fileToUpload)
This piece of code reads the file but the backend part says "Zero byte long file". Do you have any solutions? Thanks!
I assume that on the server side you have the BodyHandler in your router:
router.route().handler(BodyHandler.create());
Now depending on the way you upload your file it might end up in 2 places:
If it is a multipart/form-data it will be available in the context under the fileUploads() getter:
Set<FileUpload> uploads = routingContext.fileUploads();
// Do something with uploads....
It it is a body upload, for example something like AJAX call application/json it ends up in the body() getter. So be sure that you're using the right headers as they are processed differently both by the browser and server during the upload.
SOLVED! The problem was in Frontend. I rewrote the upload method using the XMLTHTTPRequest this way:
onUploadFile = (e) =>{
if(!e.target.files || e.target.files.length==0) return
const {reduxApi, setError, setWait, removeWait} = this.context
const fileToUpload = e.target.files[0]
setWait()
const xhr = new XMLHttpRequest()
xhr.open('POST', getRootURL() + "/cryptoindex/index/excel")
xhr.onload = () => {
removeWait()
if(xhr.status!=200){
setError({...xhr, responseJSON: JSON.parse(xhr.responseText)})
}else{
this.refreshAll()
}
}
xhr.send(fileToUpload)
}
It is not the best solution but it works!

Bad request status 400. Required String parameter 'login' is not present

I am trying to build login with react.js and connect it to my springboot.
Here is my code, react.js:
import React from 'react';
export default class Login extends React.Component {
constructor() {
super();
this.state = {
login:"",
password:""
}
}
// This will be called when the user clicks on the login button
login(e) {
e.preventDefault();
console.log(this.state.password)
function createCORSRequest(method, url) {
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
// Check if the XMLHttpRequest object has a "withCredentials" property.
// "withCredentials" only exists on XMLHTTPRequest2 objects.
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined") {
// Otherwise, check if XDomainRequest.
// XDomainRequest only exists in IE, and is IE's way of making CORS requests.
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
// Otherwise, CORS is not supported by the browser.
xhr = null;
}
return xhr;
}
var xhr = createCORSRequest('POST', "http://localhost:8080/test/login");
if (!xhr) {
throw new Error('CORS not supported');
}
fetch().then(r => r.json())
.then(data => console.log(data))
.catch(e => console.log(e))
}
render() {
return (
<form role="form">
<div>
<input type="text" name="login" placeholder="Username" />
<input type="password" name="password" placeholder="Password" />
</div>
<button type="submit"onClick={this.login.bind(this)}>Login</button>
</form>
);
}
}
And this is my springboot code that is located as TestController:
#RestController
public class TestController {
#RequestMapping(value = "/test/login", method = RequestMethod.GET )
public Boolean testLogin(#RequestParam String login, #RequestParam String password) {
if ( login.equals ("ajt"))
return true;
else {
return false;
}
}
Each of them are present in two different ports, react on :9000 and springboot on :8080.
Also, on my react page I get the error:
TypeError: Failed to execute 'fetch' on 'Window': 1 argument required, but only 0 present.(…)
Any ideas?
for info: I have only got 6 months coding behind me -_- please be kind!
There's a handful of small mistakes here. I'll try to point you in the right direction for a few of them.
First of all, I just wouldn't use fetch. It's listed as an expiremntal technology by MDN, and it's browser support is weak. For someone just starting out with web development, you're much better off using a more established and "safe" technology. Either simple XMLHTTP ajax or using the ajax method from jquery. You already seem to be going down the path of XMLHTTP, so I would suggest just replacing your fetch commands with the example I linked above.
Second, you're using two different HTTP methods. Your ajax object is going to send a POST command, but your server is listening for a GET command. This is set up on the following lines;
var xhr = createCORSRequest('POST', "http://localhost:8080/test/login");
#RequestMapping(value = "/test/login", method = RequestMethod.GET )
If you want those two pieces of code to talk to one another, they need to be set to the same method. In this case, you want a POST for both, but it's worth learning the distinction between the two for the future.
Lastly, there's the issue of getting information from your inputs and into your ajax XMLHTTP object. To do this, you're going to want to set up onChange hooks on the inputs, and attach them to a handleChange function within the react component. Use this function to save the values to the component's state, and then later take the values out of state to apply to the xmlhttp.
A simple example of what I am describing;
render() {
return (
<div>
<input type="text" name="login" onChange={this.handleChange}/>
<button onClick={this.login.bind(this)}>Login</button>
</div>
);
}
handleChange(value) {
this.setState({login: value});
}
login () {
var xmlhttp = new XMLHttpRequest(); // new HttpRequest instance
xmlhttp.open("POST", "/test/login");
xmlhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xmlhttp.send(JSON.stringify({login: this.state.login}));
}

Resources