I'm doing a Post/Redirect/Get thingy, using thymeleaf and Spring MVC and it works fine. Apart from when I get to the page doing the GET and do a refresh, the ModelAttribute is reset. Here's a snippet of something similar I'm doing.
#RequestMapping("/site")
public class SiteController {
#RequestMapping(values = {"", "/"}, method = RequestMethod.GET)
public String index(#ModelAttribute("form") Form form, Model model) {
return "/site/index";
}
#RequestMapping(values = {"/postit"}, method = RequestMethod.POST)
public String indexPoster(#ModelAttribute("form") Form form, Model model, RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("form", form);
return "redirect:/site/result";
}
#RequestMapping(values = {"/result"}, method = RequestMethod.GET)
public String indexResult(#ModelAttribute("form") Form form, Model model) {
// This will print the form, but if I do a page reload, the form properties will be reset.
System.out.println(form);
return "/site/result";
}
}
My Form object is just a simple java bean with one String property called name, obviously my real form has a buttload of properties, but this is for simplicity in the question.
On my html page for /site/index I have a simple form using th:object="${form}" and th:field="*{name}" that does a post to /postit. And on my /site/result I just output the name entered in the form. (so it's sort of a Hello World at the moment :) ).
This flow works for me, but if I hit refresh after the /site/result has been loaded, the form variable in indexResult gets reset.
I've tried using
private Form form;
#ModelAttribute("form")
public Form getForm() {
if (this.form == null) {
this.form = new Form();
}
return this.form;
}
on the class level, but it feels "hacky". And if I navigate away from the page, do other stuff and then return the old data will still be there.
In this project we are refraining (as far as we can) to use the session to store data.
What you are experiencing is the exact behavior of flash attributes. They are stored in the session right before the redirect, then loaded as model attributes after the redirect and removed from the session immediately. They are only present for a single request after redirecting.
Unfortunately there doesn't seem to be a very elegent way of handling requests to a request mapping that requires certain flash/model attributes to be present. One way of doing it would be adding a flash attribute with a predefined key/value. This will be present as a model attribute after the redirect. If it's missing you can take action accordingly (e.g. redirect to the initial form).
If you want the form data to be available after multiple refreshes of the result page you have to either use the session or send them as request attributes through the URL although the latter is not really an elegant option.
A word of caution though: Your "sollution" of storing the form as a private will not work at all. Remember that spring controllers are singleton beans so now this single form model instance will be used for every concurrent request on your site effectivly making them share the submitted data.
Related
I have a page, rendered by an angularjs controller, that shows the details of a user on page load.
This list is given by the controller of Spring:
#RequestMapping(value = "/contact/{id}", method = RequestMethod.GET, headers = "Accept=application/json")
public User getContact(#PathVariable String id) {
User user = userService.findById(id);
return user;
}
Which is requested by angularjs inside a details controller:
$http.get(urlBase+'/contact/'+$routeParams.userid).success(function(data) {
$scope.user = data;
});
Now I also want to show, on the same page, other details such as whether the visitor on that page is friends with that user. This information should be loaded while the page loads (same as retrieving the details of the user)
Should I use a separate get method to do this kind of check and put it under the first get method for example? E.g.
#RequestMapping(value = "/isFriends/{userId}", method = RequestMethod.GET, headers = "Accept=application/json")
#ResponseBody
public RETURNWHAT loadProfile(#PathVariable String userId, RequestParameter request) {
/*
* Do some checks whether a user is friends with the user on the page.
*/
request.setParameter("isFriends", true); //Perhaps not a good idea to use set parameter to retrieve this inside the angularjs' controller?
}
Other get:
$http.get(urlBase+'/isFriends/' + $routeParams.userid).success(function(data) {
});
Or is there a better way to achieve this?
I thought of only calling 1 get method that retrieves this information from 1 method in the controller. But then I assume that the controller has to send back a map with all the details.
Edit:
I believe using 1 method in the controller of Spring which returns a Map<String, Object> and 1 get function in angularjs should be the best option, am I correct?
Actually is a trade off between no of http requests and amount data being sent back from server, so as specific to your user case, its just one field you want, piggybacking to existing response is better way than making two requests.
I agree with #vinayakj that it is more efficient to combine the data in one HTTP request (less server load, less bandwidth). Definitely makes sense if you expect alot of these requests. But instead of adding the "isFriends" information to User, I'd wrap both in an "aggregate" object:
class UserDetails {
User user;
boolean isFriends;
}
The resulting JSON might look like this:
{
user: {
id: 1,
name: "abc"
},
isFriend: true
}
Why not just adding "isFriends" to User? Because what we are doing here is solely a webservice performance optimization. It should not make your model less maintainable.
Why not Map<String, Object>? Those maps are pretty generic - it would be harder to see which response contains which kind of data. A dedicated class adds type-safety and documentation. Also, you may want to annotate the components separately.
The obvious drawback is that you need to create extra classes with somewhat funny names.
I have a page with pagination links at the top.
When I click the pages it takes me from record 1-50, 51-100 and so on.
I am having issue when i click the second action like when I click page # 2 #ModelAttribute values gets null.
this is tha page url: http://localhost:8080/tax/taxedYear.html?p=2
It takes me to spring controller class with /taxedYear.html and the method is as below:
#RequestMapping(value = "/taxedYear.html", method = RequestMethod.GET)
public ModelAndView showTaxResults(#ModelAttribute("criteria")
Criteria criteria, Model model, HttpSession session, HttpServletRequest request) {
String src = criteria.getSource();
System.out.println("src === "+src);
//....
//
}
When it is called anything from criteria is null. The same method is called from the previou page and it works fine.
This happens only when I click the page urls which also calls the same method in the controller and sends page # in addition.
From Spring reference:
An #ModelAttribute on a method argument indicates the argument should be retrieved from the model. If not present in the model, the argument should be instantiated first and then added to the model.
Model is populated by controller. You assume that the model remains the same when second call is made, but apparently your assumption is wrong. Because Spring initializes the model, I believe you thought it's persistent. And it is the reasonable way, model shouldn't be persistent among HTTP calls.
Thank for Spring 3.1, I can make Post/Redirect/Get happen using RedirectAttributes.addFlashAttribute, But it seem like has small issue
here is the method that persists form object, then redirect to the view to show that form object:
#RequestMapping(value = "/{formType}/onTheEarch", method = RequestMethod.POST)
public String submitAndRedirect(SomeWebForm someWebForm,
#PathVariable("formType") String formType,
final RedirectAttributes redirectAttributes) {
// do something according to formType
// .......
redirectAttributes.addFlashAttribute("webObject", webObject);
String view = "redirect:/formType/toTheMoon";
}
here is the method to direct user to the view that shows form object
#RequestMapping(value = "/{formType}/toTheMoon", method = RequestMethod.GET)
public String submitAndRedirect(#PathVariable("formType") String formType) {
// do something according to formType
// .......
String view = "toTheMoon";
}
So far so good, but one deficiency. When I refresh the view toTheMoon, everything is gone. So the question here is
(1)How does `RedirectAttributes.addFlashAttribute` works?
(2)How can I keep the object from "FlashAttribute" even after refreshing the page?
I know for the second question, we can avoid RedirectAttributes.addFlashAttribute and just pass in any parameters in the URL to implement the RGP pattern, however, I try to avoid this as the parameter value is sensitive and should not expose to user on the browser. So what to do?
As mentioned on spring documentation chapter 17.6
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.
If you want the result to persist for few more requests, you can store them in session with some identifier, and pass only the identifier as a request parameter to the result page
Other approach I commonly do is to check whether result model exist on the result page, if not just redirect into an error page (ie: user not meant to press refresh / access the result page directly, if they do just present error). Then do a client-side refresh prevention using javascript
I have a spring controller UserController and I have the following methods (business logic not included):
#RequestMapping(value = "new", method = RequestMethod.GET)
public String getNewUserForm(Model model) {
model.addAttribute("action", "Create");
model.addAttribute(new User());
return "users/edit";
}
#RequestMapping(value = "create", method = RequestMethod.POST)
public String getCreateUser(#ModelAttribute("user") User user, BindingResult result, RedirectAttributes attr) {
attr.addFlashAttribute("user", user);
return "redirect:view";
}
#RequestMapping(value = "view", method = RequestMethod.GET)
public String getViewUser(Model model) {
return "users/view";
}
I end up with three "actions" (new, create, and view). What I want to achieve is the following: app.com/users/new is entered in the browser. The create user form users/edit is displayed. When the form is submitted, it calls users/create (POST). When the user is created successfully, I would like to forward the user to users/view, and display the input data in a non-editable page. I can't just return view because then the url remains app.com/users/create. I'd like for it to read app.com/users/view.
Right now, I'm using a RedirectAttributes object, combined with returning redirect:view and it works. But it feels like there should be a better way to accomplish this. I tried using redirect:view without RedirectAttributes and the model wasn't available.
The other alternative is to pass the user id in the url (for example: app.com/users/view/1) and load up the data from the database. But that seems like an unnecessary round trip to the database, considering I already have the latest version of the object. I will be implementing this for "pretty urls", but I wan't to have the option to not load the user object if it's available.
Something like this:
#RequestMapping(value = "view", method = RequestMethod.GET)
public String getViewUser(Model model, #RequestParam(required = false) String id) {
if(!model.containsAttribute("user")){
//get it from the db with the optional request parameter
}
return "users/view";
}
Is my approach recommended? Or am I looking at this completely wrong?
The better way is to redirect to app.com/users/view/1.
First because you have the latest changes you did to the model, but not the ones another concurrent user might have done concurrently.
Second because it makes your app stateless, thus more scalable and simpler at the same time.
Third because the view page might need more information about the user than the edit page does. It's pretty frequent to have a large detail page showing many associations of the user, but have only some modifiable information in the edit page. So saving the model of the edit page to make it available to the view page won't be sufficient.
Fourth because this way, the logic to show the detail of a user is at a single place, and there is only one possible URL to do that.
Fifth because this way, the user can bookmark the URL, or send it to someone by email, and it will work if he comes back later to this URL. Whereas in your current solution, the URL only works if you've edited the user just before.
I've been having a problem regarding using AJAX with Spring MVC. I have a form which has a lot of fields, and each field retrieves data depending on the associated button that was clicked.
So, each one of my buttons needs to call an AJAX request. Each response will be displayed on the associated field.
I wonder if it is possible to call a different method in my Spring controller once I clicked on a different button?
In other words, I want to make multiple ajax requests to the same controller where each request will call a different method in that same controller.
See this example :
// when get account detail is clicked it will call this method
#RequestMapping(method=RequestMethod.POST)
public #ResponseBody String getAccountDetails(#RequestParam(value="accountid") String accountid){
return somefunct.getAccountDetails(accountid);
}
// when get account summary is clicked it will call this method
#RequestMapping(method=RequestMethod.POST)
public #ResponseBody String getAccountSummary(#RequestParam(value="accountid") String accountid){
return somefunct.getAccountSummary(accountid);
}
/* when submit button is clicked... Form is submitted for saving*/
#RequestMapping(method=RequestMethod.POST)
public String submitForm(){
// save here
return "myform";
};*/
Currently, I can have only one AJAX request. How can I modify this code so that I can have different functionality for different AJAX requests?
First, consider that when you retrieve data from a server without modifying the state of that server, the commonly accepted standard is to use the HTTP GET method, not POST. Thus, for your first two methods, you are misusing the HTTP Methods.
Second, you can map individual URL patterns to a specific method using the value property of the RequestMapping annotation.
Third, the most RESTful way to represent your account details resource is to use the PathVariable annotation and include your identifying accountid in the actual path:
#RequestMapping(value="/account/{accountid}/details", method = RequestMethod.GET)
public #ResponseBody String getAccountDetails(#PathVariable(value="accountid") String accountid){
return somefunct.getAccountDetails(accountid);
}
Next, you can represent your account summary using a different URL pattern where the URL is built like a tree, where the first two parts of the path are once again "Account" and the accountid:
// when get account summary is clicked it will call this method
#RequestMapping(value="/account/{accountid}/summary", method=RequestMethod.GET)
public #ResponseBody String getAccountSummary(#PathVariable(value="accountid") String accountid){
return somefunct.getAccountSummary(accountid);
}
Now, your submit method, on the other hand, has side effects. This is just a fancy way of saying that the state of your server will be different at the end of this request, and any GET requests made to that resource will be different than they were prior to the change. The appropriate HTTP method to use when modifying a resource or adding a resource to a collection is the HTTP POST Method. When replacing a collection, the HTTP Method PUT is the generally accepted method of choice.
Another differentiating factor between PUT and POST is that PUT is idempotent, meaning that the same request repeated over and over again doesn't change the state on the server. If hitting the same request multiple times creates more records, then use POST.
Lastly, this request can be mapped to a URL as well. In the example below, I've assumed you are creating a new Account record and inserting a new record in the collection of accounts in the database. Thus, I've used POST. I also modified your parameter list to use PathVariable to take the accountid from the URL path, and I added a RequestBody annotation so that you can send an object in the body of the request, which could be deserialized into a Java object:
/* when submit button is clicked... Form is submitted for saving*/
#RequestMapping(value="/account/{accountid}", method=RequestMethod.POST)
public String submitForm(#PathVariable String accountid, #RequestBody Account account){
// save here
return "myform";
}
For more information on Spring MVC, please check out the Spring documentation on Spring MVC.