In my controller advice I have put one model object in HttpSession session:
Map<String, Game> gamesMap = new HashMap<>();
gamesMap.put("1", new Game());
Games games = new Games();
games.setGames(gamesMap);
session.setAttribute("games", games);
When I try to get that object with #ModelAttribute parameter games.getGames() returns me always null instead of the gamesMap I've set before.
RequestMapping(value = "test", method = RequestMethod.GET)
public String test(#ModelAttribute("games") Games games) {
games.getGames(); // getGames() returns null instead of collection I've set before.
}
Am I doing something wrong? When I use session and get it from there it works fine, but I'm trying to understand why it doesn't work with #ModelAttribute which is more cleaner.
Ok, I found what the issue was. Spring only tries to bind model attribute to session parameter if the controller is annotated with #sessionattributes. If it doesn't find that it creates new instance in which case the map was null.
Related
Is BindingResult useful to bind just exceptions with view, or something else?
what is the exact use of BindingResult?
Or is it useful in binding model attribute with view.
Particular example: use a BindingResult object as an argument for a validate method of a Validator inside a Controller.
Then, you can check this object looking for validation errors:
validator.validate(modelObject, bindingResult);
if (bindingResult.hasErrors()) {
// do something
}
Basically BindingResult is an interface which dictates how the object that stores the result of validation should store and retrieve the result of the validation(errors, attempt to bind to disallowed fields etc)
From Spring MVC Form Validation with Annotations Tutorial:
[BindingResult] is Spring’s object that holds the result of the
validation and binding and contains errors that may have occurred. The
BindingResult must come right after the model object that is validated
or else Spring will fail to validate the object and throw an
exception.
When Spring sees #Valid, it tries to find the validator for the
object being validated. Spring automatically picks up validation
annotations if you have “annotation-driven” enabled. Spring then
invokes the validator and puts any errors in the BindingResult and
adds the BindingResult to the view model.
It's important to note that the order of parameters is actually important to spring. The BindingResult needs to come right after the Form that is being validated. Likewise, the [optional] Model parameter needs to come after the BindingResult.
Example:
Valid:
#RequestMapping(value = "/entry/updateQuantity", method = RequestMethod.POST)
public String updateEntryQuantity(#Valid final UpdateQuantityForm form,
final BindingResult bindingResult,
#RequestParam("pk") final long pk,
final Model model) {
}
Not Valid:
RequestMapping(value = "/entry/updateQuantity", method = RequestMethod.POST)
public String updateEntryQuantity(#Valid final UpdateQuantityForm form,
#RequestParam("pk") final long pk,
final BindingResult bindingResult,
final Model model) {
}
Well its a sequential process.
The Request first treat by FrontController and then moves towards our own customize controller with #Controller annotation.
but our controller method is binding bean using modelattribute and we are also performing few validations on bean values.
so instead of moving the request to our controller class, FrontController moves it towards one interceptor which creates the temp object of our bean and the validate the values.
if validation successful then bind the temp obj values with our actual bean which is stored in #ModelAttribute otherwise if validation fails it does not bind and moves the resp towards error page or wherever u want.
From the official Spring documentation:
General interface that represents binding results. Extends the
interface for error registration capabilities, allowing for a
Validator to be applied, and adds binding-specific analysis and model
building.
Serves as result holder for a DataBinder, obtained via the
DataBinder.getBindingResult() method. BindingResult implementations
can also be used directly, for example to invoke a Validator on it
(e.g. as part of a unit test).
BindingResult is used for validation..
Example:-
public #ResponseBody String nutzer(#ModelAttribute(value="nutzer") Nutzer nutzer, BindingResult ergebnis){
String ergebnisText;
if(!ergebnis.hasErrors()){
nutzerList.add(nutzer);
ergebnisText = "Anzahl: " + nutzerList.size();
}else{
ergebnisText = "Error!!!!!!!!!!!";
}
return ergebnisText;
}
As Spring Specification said, #ModelAttribute will executed before the mapping handler and #SessionAttribute will keep the model attribute in session.
Consider below scenario: form bean is created after the controller is called and is set as session attribute as well. Next time MenuController is called, createForm() will be executed again and create another new form bean. My question is: will this latest created form bean be set as session attribute? and which form bean will be bind to the parameter in method bookList()?
Hope you guys can help. Thank you.
#Controller
#RequestMapping("/store")
#SessionAttribute("form")
public class MenuController {
#ModelAttribute("form")
public Form createForm() {
return new Form();
}
#RqeustMapping("/book")
public String bookList(#ModelAttribute("form") Form form){
//processing the form
}
}
When the bookList method is invoked for the first time in a given session, then method with #ModelAttribute('form) is invoked, the returned value (Form object) is stored in HttpSession and finally the bookList method is invoked with the same Form object passed as an argument (obtained from session).
For the subsequent requests within the same HttpSession, Spring retrieves the same Form object from the session and doesn't call the method with #ModelAttribute('form') again till the end of the session.
After each end of the bookList method invocation Spring stores updated version of Form object in HttpSession.
If you are using Spring Boot 2.x you can debug DefaultSessionAttributeStore#retrieveAttribute method to understand this behaviour.
Remember that your mapping is generalised. It will map both to a GET method and a POST method.
If your request mapping is a GET method,
The session attribute will hold the value of the #ModelAttribute("form") from the method createForm.
If an attribute form is returned from a POST request,
The session Attribute will override the #Model Attribute from the createForm method.
It is helpful to remember that the #ModelAttribute will execute before the mapping handler.
the sessionAttribute indicates that the "form" will be saved in the session. not meaning the "form" is retrieved from the session.
I'm writing an application with Spring framework and I have a question how to do a form validation with extra fields.
I'm new to this but as far as I understand to fill the form with form:form tag I set attribute with
#RequestMapping(value = "/register", method = RequestMethod.GET)
public String register(Model model) {
model.addAttribute("tenant", new Tenant());
return "register";
}
Then create validator class and use it on POST request:
#RequestMapping(value = "/register", method = RequestMethod.POST)
public String registerPost(#ModelAttribute("tenant") Tenant tenant,
Model model,
BindingResult result) {
TenantValidator validator = new TenantValidator();
validator.validate(tenant, result);
if (result.hasErrors()) {
return "register";
}
tenantService.save(tenant);
return "redirect:accountOverview";
}
It works very well and I'm fascinated by how convenient this is!
The only problem is what do I do with extra fields?
For example I have "repeat password" field.
If I create extra fields without using tags and by using and validate them directly I would not be able to use:
<form:errors path="repeatPassword>
tag as 'repeatPassword' is not a member of form object.
The first solution that comes to mind is to create special form object TenantDTO that would hold those extra fields and on save just transfer the data to 'Tenant' (entity bean).
Is this a good approach? What are the best practices in this situation?
Thanks!
Leonty
You can still have these values in the form object. I guess you don't want to save these to the database. You can avoid that by using the annotation Transient (http://docs.oracle.com/javaee/5/api/javax/persistence/Transient.html). This will give a leverage for you to do the path mapping, having in the form object and do all the validation but still dont save in the database. hope it helps.
I'm new to Spring MVC and trying to get a Post/Redirect/Get pattern working. We're trying to implement a survey where each page can display a variable number of questions. The way I'd like to implement this is a GET handler that prepares the next survey page and then hands that off to the view. In the same Controller, have a Post handler that processes the form's answers to the survey questions, submits that to the survey service, which returns the next page of questions, and then redirects that next surveyPage to the getNextPage GET handler.
Most of it is working, except the problem is that I don't know how to hand that 'next survey page' object from the POST handler to the getNextPage GET handler in the redirect. The redirect is working; it goes from the POST method to the GET method, but the surveyPage ModelAttribute is a new object in the GET method, and not the one that was set at the end of the POST method. As you can see, I've tried using ModelAttribute, but it doesn't work. I also tried using #SessionAttributes above the class, but then got a HttpSessionRequiredException.
We didn't know how to handle the dynamic form containing a variable # of questions with Spring MVC Forms, so we just did straight JSTL. It's funky but it works. That funkiness is what resulted in using the #RequestBody and SurveyPageBean coming back with the Post. Honestly, I don't know how the SurveyPageBean is populated. It looks like some Spring MVC magic, but it's working so I'm leaving it alone for now (another developer did this and then I picked it up, and we're both new to Spring MVC). Please don't get distracted by the unusual form handling, unless that is part of the problem with the empty surveyPage ModelAttribute not being redirected.
Here's the Controller snippet:
#Controller
#RequestMapping("/surveyPage")
public class SurveyPageController{
#RequestMapping(method=RequestMethod.GET)
public String getNextPage(#ModelAttribute("surveyPage") SurveyPage surveyPage, Model model) {
if(surveyPage.getPageId() == null) {
// call to surveyService (defined elsewhere) to start Survey and get first page
surveyPage = surveyService.startSurvey("New Survey");
}
model.addAttribute("surveyPage", surveyPage);
return "surveyPage";
}
#RequestMapping(method=RequestMethod.POST)
public String processSubmit(#RequestBody String body, SurveyPageBean submitQuestionBean, Model model, #ModelAttribute("surveyPage") SurveyPage surveyPage) {
// process form results, package them up and send to service, which
// returns the next page, if any
surveyPage = surveyService.submitPage(SurveyPageWithAnswers);
if (results.getPageId() == null) {
// The survey is done
surveyPage = surveyService.quitSurvey(surveyId);
return "redirect:done";
}
model.addAttribute("surveyPage ", surveyPage );
return "redirect:surveyPage";
}
Use Flash Attributes as shown in Warlock's Thoughts.
#RequestMapping(method = RequestMethod.POST)
public String handleFormSubmission(..., final RedirectAttributes redirectAttrs) {
...
redirectAttrs.addFlashAttribute("AttributeName", value);
return "redirect:to_some_url_handled_by_BController";
}
Your GET takes the surveyPage as a model attribute, which means it is reading it from the URL. In the POST, rather than adding the surveyPage to the model (which is lost because you are telling the client to redirect, which creates a new request and therefore a new model) you should add the surveyPage as a query parameter in your "redirect:surveyPage" You'll have to look at how the surveyPage is constructed from the query params in order to know what to put on the query string.
If, for instance, the surveyPage is constructed from a user, page number, and question count or something, I believe you could do something like "redirect:surveyPage?userId=1234&pageNumber=5678&questionCount=12 in order to pass that model attribute along.
I have a Spring MVC controller with an action that's called using AJAX.
#SessionAttributes({"userContext"})
public class Controller
{
...
#RequestMapping(value = "/my-url", method= { RequestMethods.POST })
public ModelAndView doSomething(#ModelAttribute("userContext") UserContext context,
SessionStatus sessionStatus)
{
BusinessObject obj = doSomeBusinessLogic(context.getUserName());
sessionStatus.setComplete();
ModelAndView mav = new ModelAndView("jsonView");
mav.addObject("someInt", obj.getId());
return mav;
}
}
When I run this action, I get the following exception:
net.sf.json.JSONException: There is a cycle in the hierarchy!
at t.sf.json.util.CycleDetectionStrategy$StrictCycleDetectionStrategy.handleRepeatedReferenceAsObject(CycleDetectionStrategy.java:97)
at net.sf.json.JSONObject._fromBean(JSONObject.java:833)
at net.sf.json.JSONObject.fromObject(JSONObject.java:168)
at org.springframework.web.servlet.view.json.writer.jsonlib.PropertyEditorRegistryValueProcessor.processObjectValue(PropertyEditorRegistryValueProcessor.java:127)
at net.sf.json.JSONObject._fromMap(JSONObject.java:1334)
Truncated. see log file for complete stacktrace
After doing some debugging I found out that Spring is placing the UserContext object onto the ModelAndView that I am returning. If I hard-code my user name and remove the context object from the method's parameters, the action runs successfully. Is there a way to configure Spring to omit the ModelAttribute-annotated parameters from the returned ModelAndView? As you can see, sessionStatus.setComplete() has no effect.
I've had similar problems in the past with #SessionAttributes. By declaring #SessionAttributes({"userContext"}) you're telling Spring that you want "userContext" to always be available in the model, and so Spring has no choice but to send your UserContext object out to the model, just in case you're going to be redirecting or doing something else which might end up at another Controller.
The "solution" (and I didn't like it much, but it worked) was to omit the #SessionAttributes annotation on the controller, add an HttpSession parameter to the necessary methods and "manually" manage what's in it.
I'm interested to see if there's a better way, because it seems #SessionAttributes has tremendous potential to tidy up controller-level code.
I registered a WebArgumentResolver to get to my session variable. This allowed me to keep this session variable out of the response while keeping my action unit testable.
Along with #ModelAttribute, pass #ModelMap as a method argument.
Based on business logic, error conditions -- if you do not need the attribute for certain scenarios, then remove it from the map.
public ModelAndView foo(#ModelAttribute("userContext") UserContext, #ModelMap map){
if(success){
return success.jsp
}
else{
map.remove("userContext");
return "error.jsp"
}
}
Not totally satisfied with having to pass the ModelMap as well, but I did not find any other easier way of doing it.
Cheers!!