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.
Related
I have REST API which I wrote on Spring Boot.
I want to write a frontend for it using React. To begin with, I decided to make sure that my frontend correctly receives data from the API.
Here is my rest controller code:
#RestController
#RequestMapping("/api/restaurants")
public class RestaurantRestController {
private final RestaurantService restaurantService;
public RestaurantRestController(RestaurantService restaurantService) {
this.restaurantService = restaurantService;
}
#GetMapping
// #PreAuthorize("hasAuthority('everything:read entries')")
#ApiOperation("Get all restaurants")
public ResponseEntity<List<RestaurantDto>> getAll() {
List<RestaurantDto> result = restaurantService.findAll().stream()
.map(RestaurantDto::fromRestaurant)
.collect(Collectors.toList());
return new ResponseEntity<>(result, HttpStatus.OK);
}
I wrote a configuration class on the backend side:
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000/")
.allowedMethods("GET", "POST, "PUT", "DELETE")
.allowCredentials(true).maxAge(3600);
}
}
And I access my controller from the frontend with this method:
import axios from 'axios';
const REST_URL = "http://localhost:8080/api/restaurants";
export default class RestaurantsAPI {
static async getAll() {
const response = await axios.get(REST_URL);
return response.data
}
}
And I get the correct answer.
Then I add credentials to my request:
import axios from 'axios';
const REST_URL = "http://localhost:8080/api/restaurants";
export default class RestaurantsAPI {
static async getAll() {
const response = await axios.get(REST_URL, {
auth: {
username: 'admin', password: 'admin'
}
});
return response.data
}
}
Now I see this in the console:
enter image description here
At the same time, it doesn’t matter, I leave access to the method open to everyone, using this configuration:
.antMatchers(HttpMethod.GET, "/api/restaurants").permitAll()
Or vice versa, I comment this configuration and apply an annotation to the method:
#PreAuthorize("hasAuthority('everything:read entries')")
I still get the same error.
Thanks to Postman and JUnit Tests, I know that Spring Security is configured and working properly.
Please explain what is the reason for this behavior and what should I do to fix it. Thank you.
I'm new to Salesforce and I'm stuck with showing an imaged added by ContentReference, when adding the image in the Experience Builder it returns a Content Key like this "MCTYRWQGOBCVHMHHLCSYZ2PWXQVQ", but how can I use it to show the selected image in the builder and in the web page I'm building? I tried this solution (https://salesforce.stackexchange.com/questions/333877/spring21-use-cms-content-in-lwc) and adapted it but it throws me the following error :
app:9 [webruntime] router level error
error: Proxy {}
wcstack:
<webruntime-app>
<webruntime-router-container>
<webruntimedesign-component-wrapper>
<webruntimedesign-design-component>
<webruntimedesign-component-wrapper>
<webruntimedesign-design-component>
<c-my-first-l-w-c>
<lightning-layout-item>
Not sure what is happening or what shoul I do, again I'm very new to salesforce. This is my code:
HTML:
<template>
<p>this is the leadlist {contentId}</p>
<img src={contentId} data-contentkey={contentId} class="image"></img>
<lightning-button variant="brand" label={bntLabel} title="Primary action" onclick={handleClick} class="slds-m-left_x-small"></lightning-button>
</template>
JS:
import getManagedContentByContentKeys from '#salesforce/apex/leadList.getManagedContentByContentKeys';
import { LightningElement, api, wire } from 'lwc';
export default class LeadList extends LightningElement {
#api bntLabel;
#api contentId;
handleClick = () => {
console.log("You clicked me!")
console.log('contentId', this.contentId)
}
#wire(getManagedContentByContentKeys, { managedContentIds: this.contentId})
managedContent({ error, data }) {
console.log('it entered the function:');
if (data) {
console.log('data:');
console.log({data});
// Assign data to a variable which you can use in your HTML.
} else if (error) {
console.log('error:', error);
// Handle the error.
}
}
}
Metadata:
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightningCommunity__Page</target>
<target>lightningCommunity__Default</target>
</targets>
<targetConfigs>
<targetConfig targets="lightningCommunity__Default">
<property name="bntLabel" type="String" default="click"></property>
<property type="ContentReference" name="contentId" label="Content ID"></property>
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
Apex class:
public with sharing class leadList {
public leadList() {
}
#AuraEnabled(cacheable=true)
public static String getManagedContentByContentKeys(String communityId, String[] managedContentIds, Integer pageParam, Integer pageSize, String language, String managedContentType, Boolean showAbsoluteUrl){
return 'hola';//ConnectApi.ManagedContent.getManagedContentByContentKeys(communityId, managedContentIds, pageParam, pageSize, language, managedContentType, showAbsoluteUrl);
}
}
It looks like there are a few minor bugs in the code and that is the reason you aren't having much luck.
Firstly, the #wire method in the LWC requires an array of managedContentIds, not just a single one. The syntax for the dynamic variable is also incorrect (LWC Documentation: Wire Adapters).
import getManagedContentByContentKeys from '#salesforce/apex/leadList.getManagedContentByContentKeys';
import { LightningElement, api, wire } from 'lwc';
export default class LeadList extends LightningElement {
#api bntLabel;
#api contentId;
// Added the contentId to an array of contentIds
get managedContentIds() {
return [this.contentId]
}
handleClick = () => {
console.log("You clicked me!")
console.log('contentId', this.contentId)
}
// Fix the syntax for dynamic props for `#wire` methods.
#wire(getManagedContentByContentKeys, { managedContentIds: '$managedContentIds'})
managedContent({ error, data }) {
if (error) {
console.error('error:', error);
} else if (data) {
console.log('data: ', data);
// destructure the properties we want from the response object
const {
managedContentId,
contentNodes: { source, altText, title },
} = data
// Data here should be set to a property for the html to render
this.image = {
url: source ? source.unauthenticatedUrl : '',
alt: altText ? altText.value : '',
title: title ? title.value : '',
}
}
}
}
The apex method also returns a test string (I assume this was a mistake returning "hola"). You can also set the extra properties to null/true if you don't intend on passing them.
public with sharing class leadList {
public leadList() { }
#AuraEnabled(Cacheable=true)
public static String getManagedContentByContentKeys(String communityId, String[] managedContentIds){
return ConnectApi.ManagedContent.getManagedContentByContentKeys(
communityId,
managedContentIds,
null,
null,
null,
null,
true
);
}
}
Lastly, the html should render the this.image we set above in the javascript. Instead of assigning contentId to the <img src={contentId}/>, it should use the this.image reference, <img src={image.url} alt={image.alt}/>
<template>
<p>this is the leadlist {contentId}</p>
<img src={image.url} alt={image.alt} class="image"></img>
<lightning-button variant="brand" label={bntLabel} title="Primary action" onclick={handleClick} class="slds-m-left_x-small"></lightning-button>
</template>
This question should probably have been posted on salesforce.stackexchange.com.
I am struggling to build a secure React web app with Net 5.0 backend (all in the same project) and would like some advice.
All within the same Visual Studio project.
Eg:
Project
ClientApp (React)
Controllers (C# endpoints eg /api/data, api/filtereddata)
Program.cs
Startup.cs
(Implementation #1)
I can make the front-end login using the 'react-google-login' npm package. That works well but it doesn't protect the endpoints in the controllers (/api/data).
<div>
<GoogleLogin
clientId={clientId}
buttonText="Login with Google"
onSuccess={onSuccess}
onFailure={onFailure}
cookiePolicy={'single_host_origin'}
style={{ marginTop: '100px' }}
isSignedIn={true}
/>
</div>
I have also discovered I can verify that google token on the server by using something akin to:
const onSuccess = (res) => {
const tokenBlob = { tokenId: res.tokenId };
axios.post(`/api/auth/google`, tokenBlob)
.then(res => {
})
.catch(function (error) {
console.log("axois error", error);
})
};
[Route("/api/[controller]")]
public class AuthController : Controller
{
[AllowAnonymous]
[HttpPost("google")]
public IActionResult Google([FromBody] GoogleToken t)
{
var payload = GoogleJsonWebSignature.ValidateAsync(t.tokenId, new GoogleJsonWebSignature.ValidationSettings()).Result;
}
}
But it seems awkward to do this for every API call that the UI makes.
(Implementation #2)
I have tried doing this all in the backend (Microsoft.AspNetCore.Authentication.Google), that sort of worked with AddGoogle & AddCookie, that would re-direct to a Google login page when I tried to get data from the backend (via [Authorize]) - but I could not get React to notice that it was logged in/out.
// ConfigureServices
services.AddCors(options =>
{
options.AddDefaultPolicy(builder => {
builder.WithOrigins("https://localhost","https://accounts.google.com")
.AllowAnyHeader().AllowAnyMethod();
});
});
services.AddControllers().AddNewtonsoftJson();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(o =>
{
o.LoginPath = "/signin";
o.LogoutPath = "/signout"; // ??
o.ExpireTimeSpan = TimeSpan.FromHours(1);
})
.AddGoogle(options =>
{
options.ClientId = "";
options.ClientSecret = "";
options.SaveTokens = true;
options.CallbackPath = "/signin-google";
});
// Configure
app.UseCors();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
});
app.UseSpa(spa => {
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment()) {
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
// Home controller
public class HomeController : Controller {
[Route("/signin")]
public IActionResult SignIn() {
var authProperties = new AuthenticationProperties { RedirectUri = "/"
};
return new ChallengeResult(GoogleDefaults.AuthenticationScheme, authProperties);
}
[Authorize]
[Route("/signout")]
[HttpPost]
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync();
return Ok("Logged out");
}
}
// Api controller
[Authorize]
[Route("api/[controller]")]
public class DataController : Controller
{
private readonly ILogger<DataController> _logger;
public MatchListController(ILogger<DataController> logger)
{
_logger = logger;
}
[HttpGet]
public ResponseViewModel Get([AllowNull] DateTime? d) => new ResponseViewModel(matchDate ?? DateTime.UtcNow)?.Result;
}
The second implementation will re-direct to Google and require the login but React doesn't know the page is logged in. So how can it get the logged info to display the username etc?
So what is the best practice? Am I close? I feel close!
What I'd love to see would be an example of the WeatherForecast React template in visual studio with a working Google login that uses [Authorize] on the API data controller.
Any suggestions welcome.
Edit: Added some code
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
}
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.