Spring MVC PRG pattern with multiple tabs session workaround - spring

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).

Related

Login page with Spring

I'm setting up my security support in Spring and I am exploring what it has to offer. While setting up my login URL and view, I needed to register simple controllers for home and login URL. Concretely, code is:
#Override
public void addViewControllers(ViewControllerRegistry registry){
registry.addViewController("/").setViewName("home");
registry.addViewController("/login");
}
In line registry.addViewController("/").setViewName("home"); it seems that that line simply adds controller for URL "/" and for GET to that URL simply uses view "home" for rendering.
Did I get it right? Main question is about line registry.addViewController("/login");. Here I add controller for "/login" URL.
But how application knows which view to use? When I tried the app it really used "login" view I created.
Well, you are on the right track but not completely. If you are NOT logged in and open the URL "yourdomain.tld/" you will be redirected to the URL "yourdomain.tld/home" which normally could be a login page. (first line). Of course, you need a controller to handle this request and return a proper JSP/JSF page.
Your second line is not needed. The trick is that with such a configuration you can use the URL "yourdoamin.tld/" with a different page for logged in users.
And to answer your main question, you need a controller for every URL or part of the URL you want to use. And within this controller, you can choose the views according to the requested URL.
Example:
#Controller
#RequestMapping(value="/info")
#Scope(value="session")
public class InfoController extends AbstractWebController {
#RequestMapping
public ModelAndView infoPage() {
ModelAndView mav = this.getModelAndView("info/infopage");
return mav;
}
}
This controller is responsible for all URLs starting with "yourdomain.tld/info". And as you can see I have only the URL "yourdomain.tld/info" to return a proper view. In this example I return "infopage.jsp". You can add code to return different views for other pages.

Changing back the url to original if exception thrown and return back to form

I have a thymeleaf signup form, which if we submit then a controller at "/signup_do" is called which validates and saves the user to database:
<form action="/signup_do" method="post">
...
</form>
The controller at "/signup_do" passes the request to the accountRegistration service method, which does the validation:
#PostMapping("/signup_do")
public String register(Account account, HttpSession session) {
session.setAttribute("accountToRegister", account);
accountManagement.accountRegistration(account);
return "Success";
}
The account registration method can throw an exception SignupFormException, which is handled by the #ExceptionHandler defined in that controller class:
#ExceptionHandler(value=SignupFormException.class)
public String handle(HttpSession session, Model response) {
Account returnDataToForm = (Account) session.getAttribute("accountToRegister");
response.addAttribute("name", returnDataToForm.getFirstName());
session.invalidate();
return "signup";
}
Now the problem is that when exception occurs, the inputs entered in the form is passed back to the signup form, and the entered data remains intact, but the url still remains as /signup_do.
I have tried using return "redirect:/signup" instead, which does change the url, but it ends up making a get request to the /signup url like
/signup?name=John...
but my /signup controller is not designed to handle a get request, it just knows to display the form, so the information is lost.
#GetMapping("/signup")
public String signupPage() {return "signup";}
I also tried using forward:/signup, but that just ended up throwing 405 error.
I figured out a clean workaround a few hours after asking this question.
What I did is change the name of the controller that handles the signup process to ("/signup") as well. Since the controller that displays the page is a #GetMapping("/signup") and the one that handles the signup process is a #PostMapping("/signup") there is no clash.
Now even if the controller changes, the url remains the same, since both of them are signup...
#GetMapping("/signup")
public String signupPage() {return "signup";}
#PostMapping("/signup")
public String register(Account account, HttpSession session) {
session.setAttribute("accountToRegister", account);
accountManagement.accountRegistration(account);
return "success";
}
And this works just like I wanted!!
Redirecting will make a get request to the controller looking for the view to display, which in your situation means losing your data for the reasons you give. I can think of two workarounds:
Don't do the redirect and change the URL manually with javascript everytime you enter this view. If you dislike having a "wrong" URL in a view, editing it manually looks the most reasonable and direct approach. You can see how to do this here, including it in a script that executes everytime the page loads/the submit button is pressed.
Do the redirect and avoid losing your info by storing it in the session for a while longer, accessing it in thymeleaf in this way, instead of getting it from a model attribute. This would mean you would have to be careful to remove this session attributes later. It's also not very "clean" that your get request for the form view includes the user info, so I wouldn't go with this solution if avoidable.

FlashAttributes not in model after redirect

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).

Handle url pattern that has user name as part of the url

Suppose I have 3 url patterns that needs to be handled by Spring MVC as follows:
1) www.example.com/login (to login page)
2) www.example.com/home (to my home page)
3) www.example.com/john (to user's home page)
I would like to know what is the best practice way of handling the url pattern that has username as part of the url (real world example is facebook fanpage www.faceboo.com/{fanpage-name})
I have come up with my own solution but not sure if this is the clean way or possible to do it.
In my approach, I need to intercept the request before it being passed to Spring MVC's dispatchservlet, then query the database to convert username to userid and change the request URI to the pattern that Spring MVC can recognize like www.example/user/userId=45.
But I am not sure if this is doable since the ServletAPI does not have the setter method
for requestURI(it does have the getter method for requestURI)
Or if you have a better solution please share with me. Thank in advance :-)
Spring MVC should be able to handle this just fine with PathVariables.
One handler for /login, one handler for /home, and one handler for /{userName}. Within the username handler you can do the lookup to get the user. Something like this:
#RequestMapping(value="/login", method=RequestMethod.GET)
public String getLoginPage() {
// Assuming your view resolver will resolve this to your jsp or whatever view
return "login";
}
#RequestMapping(value="/home", method=RequestMethod.GET)
public String getHomePage() {
return "home";
}
#RequestMapping(value="/{userName}", method=RequestMethod.GET)
public ModelAndView getUserPage( #PathVariable() String userName ) {
// do stuff here to look up the user and populate the model
// return the Model and View with the view pointing to your user page
}

Spring redirect: prefix issue

I have an application which uses Spring 3. I have a view resolver which builds my views based on a String. So in my controllers I have methods like this one.
#RequestMapping(...)
public String method(){
//Some proccessing
return "tiles:tileName"
}
I need to return a RedirectView to solve the duplicate submission due to updating the page in the browser, so I have thought to use Spring redirect: prefix. The problem is that it only redirects when I user a URL alter the prefix (not with a name a resolver can understand). I wanted to do something like this:
#RequestMapping(...)
public String method(){
//Some proccessing
return "redirect:tiles:tileName"
}
Is there any way to use RedirectView with the String (the resolvable view name) I get from the every controller method?
Thanks
the call prefixed by redirect: is a url, which is sent in a standard browser 302 redirect. you can't redirect to a view, because a view isn't a url. instead you'll need a new servelet mapping to a 'success' view and then redirect to that instead
#RequestMapping("processing.htm")
public String method(){
//Some proccessing
return "redirect:success.htm"
}
#RequestMapping("success.htm")
public String method(){
return "tiles:tileName"
}
this case works fine when you just need to show a 'thank you' page, which requires no specific data from the processing stage. however, if your success page needs to show some information from the processing, there are 2 ways to do it.
1) pass the information in the url as a get post ("redirect:success.htm?message=hi"). this is incredibly hackable, and thus highly unrecommended.
2) the better way is to store information in the http session, using #SessionAttributes and #ModelAttribute

Resources