I'm using Spring 3.1.0.RELEASE. For some reason, in my controller, when I POST my form and return the original screen when an error occurs, model attributes are not getting populated like they do when I invoke the page through a GET method. In my controller I have
#Controller
public class StandardsUploadController {
…
#RequestMapping(value = "/upload")
public String getUploadForm(Model model) {
model.addAttribute(new StandardsUploadItem());
model.addAttribute("gradeList", gradeList);
model.addAttribute("subjectList", subjectList);
return "upload/index";
}
#RequestMapping(value = "/upload", method = RequestMethod.POST)
public ModelAndView processFile(final StandardsUploadItem uploadItem,
final BindingResult result,
final HttpServletRequest request,
final HttpServletResponse response) throws InvalidFormatException, CreateException, NamingException {
stdsUploadValidator.validate(uploadItem, result);
if (!result.hasErrors()) {
try {
…
} catch (IOException e) {
LOG.error(e.getMessage(), e);
e.printStackTrace();
}
} // if
return new ModelAndView("upload/index");
}
What am I doing wrong and how can I correct it?
When you return to the upload/index view from the POST, it is not re-populating the Model, since your code to populate the model is done on the GET.
One potential option is to use the #ModelAttribute annotation in your Controller class
You would have, for example, a method that looks like this for the StandardsUploadItem:
#ModelAttribute("uploadItem")
public StandardsUploadItem getStandardsUploadItem(){
return new StandardsUploadItem();
}
You could then remove the line below from your GET method:
model.addAttribute(new StandardsUploadItem());
Since a method annotated with #ModelAttribute and returning an object will automatically be put in the ModelMap, regardless of which Controller RequestMapping method is activated.
Your method signature for the POST method would contain something like this:
..., #ModelAttribute("uploadItem") StandardsUploadItem uploadItem, ...
You would need to do something similar for the other attributes in your model (gradeList, and subjectList), but since you do not seem to need them on the POST, you could do something like add a Model parameter to your POST method signature, and re-populate that Model before you return the ModelAndView in the error case.
Related
I have a controller that has read function and handles urls like this
-> /{id}/EnumElement
class FirstController {
Object read(#PathVariable UUID id, #PathVariable EnumeratedEntity value,
HttpServletRequest request)
}
I want to add the second controller that would handle only a single request
class SecondController {
-> /{id}/metadata
Object meta(#PathVariable UUID id, HttpServletRequest request)
}
I also have a controller advice that supposed to handle EnumeratedEntity values
#RestControllerAdvice
public class DefaultControllerAdvice {
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(EnumeratedEntity.class, new PropertyEditorSupport() {
#Override
public void setAsText(final String text) {
try {
setValue(EnumeratedEntity.valueOf(text));
} catch (IllegalArgumentException e) {
throw new UnknownResourceException("The requested resource is not supported", e);
}
}
});
}
The problem is that each time I request /metadata the advicer is trying to get the value of metadata (that does not exist) and throws an error. Also it seems like it takes first controller as a priority or something.
Is there a way to route /metadata request to a second controller and ignore advicer altogether?
After some search I decided to go with this solution for the first controller
#GetMapping(/{id}/{enumElement:^(?!metadata).*})
Object read(#PathVariable UUID id, #PathVariable EnumeratedEntity value,
HttpServletRequest request)
I have an issue similar to this one, the solution does not work as I wished however:
Spring MVC how to create controller without return (String) view?
I have a form which should pass the file:
example
And the controller for it:
#PostMapping("/uploadFile")
public #ResponseBody void uploadFile(Model model, #RequestParam("file") MultipartFile multipartFile) throws InterruptedException {
//, RedirectAttributes redirectAttributes) throws InterruptedException {
Reservation reservation=new Reservation( );
fileService.uploadFile( multipartFile );
File file = new File("\\car-rental\\src\\main\\resources\\static\\attachments", multipartFile.getOriginalFilename());
log.info( "name and path " + file.getName() + file.getPath() );
Picname picname=new Picname();
picname.setPicnameAsString(file.getName() );
log.info( "picname file " + picname.getPicnameAsString() );
TimeUnit.SECONDS.sleep(2);
}
}
I want the controller ONLY to perform the logic without returning anything: it returns however an empty page:
empty page returned
How can I make it not returning anything, just staying on the site with the form? The only idea was to set an delay with the .sleep(), but it would be a workaround and I would like to solve it with a cleaner solution
It is the nature of controllers to return a response since you are developing an MVC application which will receive POST requests to the endpoint you specified.
What you can do is declare the controller to be a #RestController which returns a ResponseEntity indicating that everything went OK or any other appropriate response in case some failure happens.
#RestController
public class ControllerClassName{
#PostMapping("/uploadFile")
public ResponseEntity<?> uploadFile(Model model, #RequestParam("file") MultipartFile multipartFile) throws InterruptedException {
try{
// logic
return ResponseEntity.ok().build();
}catch(Exception e){
return ResponseEntity.badRequest().build();
}
}
}
To address your issue you may need to change the return type of your function.
Using a ResponseEntity return type may be more appropriate than using a ResponseBody return type.
I am creating a very basic application with SpringBoot and Thymeleaf. In the controller I have 2 methods as follows:
Method1 - This method displays all the data from the database:
#RequestMapping("/showData")
public String showData(Model model)
{
model.addAttribute("Data", dataRepo.findAll());
return "show_data";
}
Method2 - This method adds data to the database:
#RequestMapping(value = "/addData", method = RequestMethod.POST)
public String addData(#Valid Data data, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
return "add_data";
}
model.addAttribute("data", data);
investmentTypeRepo.save(data);
return "add_data.html";
}
HTML files are present corresponding to these methods i.e. show_data.html and add_data.html.
Once the addData method completes, I want to display all the data from the database. However, the above redirects the code to the static add_data.html page and the newly added data is not displayed. I need to somehow invoke the showData method on the controller so I need to redirect the user to the /showData URL. Is this possible? If so, how can this be done?
Try this:
#RequestMapping(value = "/addData", method = RequestMethod.POST)
public String addData(#Valid Data data, BindingResult bindingResult, Model model) {
//your code
return "redirect:/showData";
}
sparrow's solution did not work for me. It just rendered the text "redirect:/"
I was able to get it working by adding HttpServletResponse httpResponse to the controller method header.
Then in the code, adding httpResponse.sendRedirect("/"); into the method.
Example:
#RequestMapping("/test")
public String test(#RequestParam("testValue") String testValue, HttpServletResponse httpResponse) throws Exception {
if(testValue == null) {
httpResponse.sendRedirect("/");
return null;
}
return "<h1>success: " + testValue + "</h1>";
}
Below Solution worked for me.
getAllCategory() method displays the data and createCategory() method add data to the database. Using return "redirect:categories";, will redirect to the getAllCategory() method.
#GetMapping("/categories")
public String getAllCategory(Model model) {
model.addAttribute("categories",categoryRepo.findAll());
return "index";
}
#PostMapping("/categories")
public String createCategory(#Valid Category category) {
categoryRepo.save(category);
return "redirect:categories";
}
OR using ajax jQuery also it is possible.
You should return a http status code 3xx from your addData request and put the redirct url in the response.
I use spring-boot as a backend server. It has tens of Action Methods. As usual Some of them contains validation. Actually I use BindingResult and returns validation error for returning Http 400 Status.
#CrossOrigin
#RestController
public class ValidationTestController {
#RequestMapping(value = {"/validation-test", "/validation-test/"}, method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<String> login(#RequestBody #Valid final TestData data, final BindingResult result) {
if (result.hasErrors()) {
return new ResponseEntity<>("Sorry incoming data is not valid!", HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>("OK!", HttpStatus.OK);
}
private static final class TestData {
#NotNull
private String value;
}
}
My aim is removing follpwing lines:
if (result.hasErrors()) {
return new ResponseEntity<>("Sorry incoming data is not valid!", HttpStatus.BAD_REQUEST);
}
IMHO it's a cross cutting concern like Authentication and Auditing. I want to handle it in a one global ErrorHandler Method. It's possible to throw a CustomValidationException Before executing the method. So I can handle the exception in ErrorController.
Yes, you can centralize the exception handling logic at one place, using #ExceptionHandler which is a ControllerAdvice from Spring.
You can look at here
I have a nasty validation problem with this annotated Spring controller (relevant parts below):
#RequestMapping(value="view")
#SessionAttributes(types = UserChoice.class)
#Controller(value="takeSurveyController")
public class TakeSurveyController {
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Answer.class, new AnswerPropertyEditor(getAnswerService()));
}
// ------------------------------------------------------------------------|
#RenderMapping(params="action=showQuestionForUserForm")
public String showQuestionForUserForm(#RequestParam("surveyID") Long surveyId,
#RequestParam(value="questionID", required=false) Long questionId,
RenderRequest request, Model model) {
// ...
return "questionForUserShow";
}
// ------------------------------------------------------------------------|
#ActionMapping(params="action=submitUserChoice")
public void submitAnswerForm(#ModelAttribute("userChoice") UserChoice userChoice,
BindingResult bindingResult, ActionRequest request, ActionResponse response) {
// ...
getUserChoiceValidator().validate(userChoice, bindingResult);
if (!bindingResult.hasErrors()) {
getUserChoiceService().save(userChoice);
// ...
}
else {
// binding errors: reload current question
response.setRenderParameter("action", "showQuestionForUserForm");
response.setRenderParameter("surveyID", survey.getId().toString());
response.setRenderParameter("questionID", currentQuestion.getId().toString());
}
// ...
}
// ....
}
The logic works perfectly. If I have a binding error, the render method is called from the action method and the page reloads.
The problem is I can't get the validation errors printed on the JSP page.
I have a similar controller implementing an add operation (AddQuestionController) and there I get the messages printed.
The differences are that this has a session object (the other doesn't) and the other has a method annotated with #ModelAttribute while this hasn't.
If I remove on the AddQuestionController the #ModelAttribute annotated method, error messages are not printed anymore.
I thought that adding a similar method on this one would solve the problem but it didn't work.
Can anyone tell me what I'm doing wrong?
Thanks!
Workaround by David Rosell on Jul 1 '11 on this question seems to solve the issue.