FlashAttributes not in model after redirect - spring

I have this controller method:
#GetMapping("/notations")
public String listAll(Model model) {
Iterable<PriceNotation> allItems = loadAllNotations();
model.addAttribute("notations", allItems);
return "supply/notations";
}
Then I have this method which redirects to the one above:
#GetMapping(value = "/notations/delete")
public String delete(#RequestParam(name="id", required=true) Long id, RedirectAttributes redirectAttributes)
{
try {
notationRepository.deleteById(id);
} catch (RuntimeException e) {
redirectAttributes.addFlashAttribute("message", "delete failed");
}
return "redirect:/notations";
}
When I put a breakpoint in the first method after a redirect, the model is empty. Although the documentation says:
After the redirect, flash attributes are automatically added to the
model of the controller that serves the target URL.
Also in my html page I have this header which should display the message:
<h2 th:text="${message}"></h2>
Also this header is empty. What am I missing?
PS, I know this question has been asked before but there was no accepted answer and none of the suggestions worked for me.

they are not added to model as they are passed as query parameters like example.com?message=abc.
So you can either:
access them in controller with #RequestParam and then add to your model
OR access them in thymeleaf with ${#param.message[0]}
in summary you should treat redirectAttributes as reqular query parameters in receiving controller (listAll).

Related

Spring MVC How to log all the errors from BindingResult

I have a controller which makes post method. The controller's method validates a entity. In case some errors occurred it redirects to error page otherwise it saves the entity. My code looks like that:
public String createEntity(Entity entity, BindingResult result) {
// Validate here
if(result.hasErrors) {
return "errorPage";
}
service.create(entity);
return "some view";
}
So now if there are errors I want to log them all. I've read this article
How to get error text in controller from BindingResult
but I don't want to type check.
Is there a clever way to do that?
Thank you.
it is very simple just add error list to your model
public String createEntity(Entity entity, BindingResult result,Model model) {
// Validate here
if(result.hasErrors) {
model.addAttribute("errors",result.getAllErrors());
return "errorPage";
}else {
service.create(entity);
return "some view";
}
}
later in your jsp :
<c:if test="${not empty errors}">
//foreach element show error (just an exampl)
</c:if>

Why I can't redirect from a Spring MVC controller method to another controller method?

I am pretty new in Spring MVC and I have some problem trying to redirect to a controller method after that another controller method terminate its execution.
So I have the following situation. Into a controller class I have this method that correctly handle POST request toward the validaProgetti resource:
#RequestMapping(value = "validaProgetti", method=RequestMethod.POST)
public #ResponseBody String validaProgetti(#RequestBody List<Integer> checkedRowList) {
System.out.println("ID progetti da aggiornare: " + checkedRowList);
List<Twp1007Progetto> progettiDaValidare = new ArrayList<Twp1007Progetto>();
for (int i=0; i<checkedRowList.size(); i++) {
System.out.println("ID PROGETTO: " + checkedRowList.get(i));
progettiDaValidare.add(progettoService.getProgetto(checkedRowList.get(i)));
}
progettoService.validaProgetti(progettiDaValidare);
return "redirect:ricercaValidazione";
}
So this method is correctly mapped and when the validaProgetti resource is called it is executed.
At the end of this method I don't return a view name that render a JSP page but I have to redirect to another method (that do something and render a JSP page). So, instead to return a view name, I redirect toward another resource:
return "redirect:ricercaValidazione";
Then in the same controller class I have declared this method that handle request toward this ricercaValidazione resource:
#RequestMapping(value = "ricercaValidazione", method=RequestMethod.POST)
public String ricercaValidazione(#ModelAttribute ConsultazioneFilter consultazioneFilter, Model model, HttpServletRequest request) {
RicercaConsultazioneViewObject filtro = null;
try {
filtro = new ObjectMapper().readValue(request.getParameter("filtro"), RicercaConsultazioneViewObject.class);
filtro.setSelStatoProgetto(3); // Progetti da validare
} catch (IOException e) {
logger.error(e);
}
consultazioneFilter = new ConsultazioneFilter(filtro);
model.addAttribute("consultazioneFilter", consultazioneFilter);
model.addAttribute("listaProgetti", new ListViewObject<Twp1007Progetto>(progettoService.getListaProgettiConsultazione(consultazioneFilter)) );
return "validazione/tabellaRisultati";
}
The problem is that it can't work and after the redirection can't enter into the ricercaValidazione() method.
I think that maybe the problem is that this ricercaValidazione() method handle POST request toward the ricercaValidazione resource and the return "redirect:ricercaValidazione"; maybe generate a GET request.
But I am not sure about it.
Why? What am I missing? How can I solve this issue?
Tnx
the redirect and fordward prefix are for resolving views; you are tring to redirect from one controller to another one. This can be done but redirect works in the following way
A response is sent to the browser with the redirect http status code and and url
The browser loads via GET the request URL
Your Spring controller (and the corresponding ammping method) is invocated if it matches the annotation params
From what you write I'm not sure this is what you really want; as you already noted there is a mismatch between HTTP methods (GET vs POST).
Your second method ricercaValidazione expects a filtro param in order to filter some data, but in the validaProgetti there is nothing similar, so it seems that the two controllers are not directly chainable. If what you want is to display a page after validaProgetti that shows a form and the the user can submit it you must add a method annotated with a method GET and url ricercaValidazione; the new method must return the view containing the form; which points via POST to url of validaProgetti. In this way you can redirect from ricercaValidazione to validaProgetti
Give mapping name of your controller with redirect like
redirect:/controll-mapping_name/ricercaValidazione
have a look on this question
Unable to redirect from one controller to another controller-Spring MVC

How to deal with error "Request method 'GET' not supported"

I would like to implement deletion of a entity from a list view. And I faced this problem. Can you explain me, what is the problem and how can I ovrcome it?
The controller method:
#RequestMapping(value = "/deleteComment/{commentId}", method = RequestMethod.POST)
public String deleteComment(#PathVariable int commentId, BindingResult result, Model model){
{
Comment deletedComment = commentService.findCommentByID(commentId);
if (deletedComment != null) {
commentService.deleteComment(deletedComment);
}
return "refresh:";
}
Do I need to specify 'Get' method, if I'm dealing with list-view (and I see the whole list). If I need, what code should I place there. I have no any ideas...
The problem is in button type attribute.
If You have HTML button type="submit" then Your service can be only RequestMethod.GET. When You change to RequestMethod.POST then You have "Request method 'GET' not supported".
SOLUTION: Change attribute in button tag to type="button".
By specifying method = RequestMethod.POST you are essentially saying that the deleteComment method should be called only for POST and for a path /deleteComment/{commentId}. If you want it to accept requests for GET also, you can just remove the method or accept GET also this way:
#RequestMapping(value = "/deleteComment/{commentId}", method = {RequestMethod.POST, RequestMethod.GET})
You may also want to explicitly specify the name of the variable to bind to the commentId path variable this way:
public String deleteComment(#PathVariable("commentId") int commentId, BindingResult result, Model model){

Spring MVC PRG pattern with multiple tabs session workaround

I have the following sequence.
View1 (POST form) -> PostController (create model and redirect) -> GetController -> View2
I am using RedirectAttributes to pass model between PostController and GetController, I have
class PostController {
public String mypost(..., final RedirectAttributes redirectAttrs){
//create model
redirectAttrs.addFlashAttribute("model", model);
return "redirect:myget";
}
}
and
#SessionAttributes("model")
class GetController {
public ModelAndView myget(#ModelAttribute("model") final Model model){
ModelAndView mav = new ModelAndView("view2");
mav.addObject("model", model);
return mav;
}
}
When a user opens multiple tabs on a browser, then refresh the earlier tab, it will be overwritten by the latter opened tab.
I would like each tab to be independent, hope someone point me to the right direction.
Thanks.
Edit
The problem is at #SessionAttributes("model"). I use it because "Flash attributes are saved temporarily before the redirect (typically in the session) to be made available to the request after the redirect and removed immediately.". Thus, tabs are overwritten each other because the model in session is updated.
Typically when I use PRG I try to put all the relevant attributes in the redirect url. Something like this...
public String myPost(ThingBean thingBean){
Thing t = myService.updateThing(thingBean);
return "redirect:thingView?id="+t.getId();
}
That way when you intercept the redirected get request you don't have to rely on any previously stored session data.
#RequestMapping(value="thingView",method=RequestMethod.Get)
public String thingView(Map<String,Object> model, #RequestParam(value="id") Integer id){
model.put("thing",myService.getThing(id));
return "thing/viewTemplate";
}
Keeping your model as a session attribute is kind of like storing your page in a global variable. It's not a good idea. And when you hit refresh on a page the get request is only going to send what's in the url (and maybe some cookie data if you're using that).

MVC3: PRG Pattern with Search Filters on Action Method

I have a controller with an Index method that has several optional parameters for filtering results that are returned to the view.
public ActionResult Index(string searchString, string location, string status) {
...
product = repository.GetProducts(string searchString, string location, string status);
return View(product);
}
I would like to implement the PRG Pattern like below but I'm not sure how to go about it.
[HttpPost]
public ActionResult Index(ViewModel model) {
...
if (ModelState.IsValid) {
product = repository.GetProducts(model);
return RedirectToAction(); // Not sure how to handle the redirect
}
return View(model);
}
My understanding is that you should not use this pattern if:
You do not need to use this pattern unless you have actually stored some data (I'm not)
You would not use this pattern to avoid the "Are you sure you want to resubmit" message from IE when refreshing the page (guilty)
Should I be trying to use this pattern? If so, how would I go about this?
Thanks!
PRG Stands for Post-Redirect-Get. that means when you post some data to the server back, you should redirect to a GET Action.
Why do we need to do this ?
Imagine you have Form where you enter the customer registration information and clicking on submit where it posts to an HttpPost action method. You are reading the data from the Form and Saving it to a database and you are not doing the redirect. Instead you are staying on the same page. Now if you refresh your browser ( just press F5 button) The browser will again do a similar form posting and your HttpPost Action method will again do the same thing. ie; It will save the same form data again. This is a problem. To avoid this problem, We use PRG pattern.
In PRG, You click on submit and The HttpPost Action method will save your data (or whatever it has to do) and Then do a Redirect to a Get Request. So the browser will send a Get Request to that Action
RedirectToAction method returns an HTTP 302 response to the browser, which causes the browser to make a GET request to the specified action.
[HttpPost]
public ActionResult SaveCustemer(string name,string age)
{
//Save the customer here
return RedirectToAction("CustomerList");
}
The above code will save data and the redirect to the Customer List action method. So your browser url will be now http://yourdomain/yourcontroller/CustomerList. Now if you refresh the browser. IT will not save the duplicate data. it will simply load the CustomerList page.
In your search Action method, You dont need to do a Redirect to a Get Action. You have the search results in the products variable. Just Pass that to the required view to show the results. You dont need to worry about duplicate form posting . So you are good with that.
[HttpPost]
public ActionResult Index(ViewModel model) {
if (ModelState.IsValid) {
var products = repository.GetProducts(model);
return View(products)
}
return View(model);
}
A redirect is just an ActionResult that is another action. So if you had an action called SearchResults you would simply say
return RedirectToAction("SearchResults");
If the action is in another controller...
return RedirectToAction("SearchResults", "ControllerName");
With parameter...
return RedirectToAction("SearchResults", "ControllerName", new { parameterName = model.PropertyName });
Update
It occurred to me that you might also want the option to send a complex object to the next action, in which case you have limited options, TempData is the preferred method
Using your method
[HttpPost]
public ActionResult Index(ViewModel model) {
...
if (ModelState.IsValid) {
product = repository.GetProducts(model);
TempData["Product"] = product;
return RedirectToAction("NextAction");
}
return View(model);
}
public ActionResult NextAction() {
var model = new Product();
if(TempData["Product"] != null)
model = (Product)TempData["Product"];
Return View(model);
}

Resources