Can't access Model attributes during validation - spring

I'm using Spring 3.1 and have web pages using validation. The field-level validation, and the display of errors, works OK. My problem is with Model attributes not being available during the validation form display.
Let's say I've code:
#RequestMapping(value="/edit", method=RequestMethod.GET)
public String getEdit(#RequestParam("id") Long id, Model model) {
model.addAttribute("mytitle", "Hello There");
return "editObject"
}
#RequestMapping(value="/edit", method=RequestMethod.POST)
public String postEdit(#RequestParam("id") Long id, #Valid #ModelAttribute("object") MyData object, BindingResult result) {
if(result.hasErrors()) {
return "editObject";
}
[snip]
}
If I have an error the hasErrors() is detected and short-circuits to the map "editObject". However, the model attributes aren't available.
What do I use here? I tried adding a Model reference to the postEdit parameter list and adding in again things like the "mytitle" attribute.
Thanks,
Jerome.

When postEdit controller render the editObject view, you are in a new request, so you are loosing the model (previously set for getEdit action).
What you need to do it re-set any values needed in a new model:
#RequestMapping(value="/edit", method=RequestMethod.POST)
public String postEdit(#RequestParam("id") Long id, #Valid #ModelAttribute("object") MyData object, BindingResult result, Model model) {
if(result.hasErrors()) {
model.addAttribute("mytitle", "Hello There");
model.addAttribute("object", object);
return "editObject";
}
If you need to keep those attributes for different views you can store them in a session for example (so that you won't have to re-set them for each new request).

Related

why Spring mvc redirect not working well?

#PostMapping("/regist")
public String regist(Person person, Model model) {
Person p = new Person("name", "age");
model.addAttribute("person", p); //add person to model
model.addAttribute("hobby", "reading);
return "redirect:/info";
}
#GetMapping("/info")
public String info() {
return "result";
}
Why (person) model.addAttribute("person", p) not appended to url like (hobby) when redirecting?
Model attributes are not used for redirects. Have a look at RedirectAttributes instead.
A specialization of the Model interface that controllers can use to
select attributes for a redirect scenario. Since the intent of adding
redirect attributes is very explicit -- i.e. to be used for a redirect
URL, attribute values may be formatted as Strings and stored that way
to make them eligible to be appended to the query string or expanded
as URI variables in org.springframework.web.servlet.view.RedirectView.
This interface also provides a way to add flash attributes. For a
general overview of flash attributes see FlashMap. You can use
RedirectAttributes to store flash attributes and they will be
automatically propagated to the "output" FlashMap of the current
request.
Example usage in an #Controller:
#RequestMapping(value = "/accounts", method = RequestMethod.POST)
public String handle(Account account, BindingResult result, RedirectAttributes redirectAttrs) {
if (result.hasErrors()) {
return "accounts/new";
}
// Save account ...
redirectAttrs.addAttribute("id", account.getId()).addFlashAttribute("message", "Account created!");
return "redirect:/accounts/{id}";
}
A RedirectAttributes model is
empty when the method is called and is never used unless the method
returns a redirect view name or a RedirectView.
After the redirect, flash attributes are automatically added to the
model of the controller that serves the target URL.

How to pass model attributes between method inside controller Spring MVC

Good day folks.
I have couple methods inside controller I want to pass model attributes between them,
First method gets data from a database:
#RequestMapping(value="/result", method=RequestMethod.GET)
public String resultHTML(#RequestParam String name, #ModelAttribute("fbosAttributes") FormBackingObjectSearch fbos,BindingResult bindingResult, Model model) throws Exception {
model.addAttribute("findAttributes", educationWebService.fetchByNam(fbos.getName()));
return "search";
And another method have to get attributes witch was created from method above:
#RequestMapping(value="/result.xls", method=RequestMethod.GET)
public String resultXLS(#ModelAttribute("findAttributes") ArrayList<FormDate> mylists, Model model) throws Exception {
model.addAttribute("findAttributesNew", mylists);
return "xlspage";
}
when I check for mylists.size() it shows/returns 0
Please help.
You can add session attributes to your controller class by this annotation:
#SessionAttributes({"findAttributes"})

Spring MVC binding extra objects

I'm getting some weird binding issue with Spring MVC 3.
My Controller request mapping looks like this:
#RequestMapping
public String save(HttpServletRequest req,
#ModelAttribute("userEditForm") UserEditForm form,
BindingResult formBindingResult,
ModelMap model,
#ModelAttribute("session") AdminSession session) {
// some validation etc
}
The UserEditForm:
public class UserEditForm {
private User user;
public User getUser() { ... }
public void setUser(User user) { ... }
}
The AdminSession:
public class AdminSession {
private User user;
public User getUser() { ... }
public void setUser() { ...}
}
What's happening is that when I submit my form, Spring is binding the User as I expect in my UserEditForm object, however, the AdminSession is also having it's User bound by Spring, in so far as it's property values are also updated.
I'm going to assume it's due to having a user property in both #ModelAttribute objects.
I thought that having the BindingResult after the UserEditForm form in the method signature would stop this? The objects are separate instances, and my form elements reference the UserEditForm object:
<#spring.bind "userEditForm.user.name" />
<input name="${spring.status.expression}" />
I've noticed that in the generated HTML it's outputting:
<input name="user.name" />
Hardcoding the name as userEditForm.user.name gives me errors, so that's not the way forward.
Is there anyway to stop this from happening?
That's the default behavior when you annotate a handler method parameter with the #ModelAttribute. Spring takes the request properties and matches them to properties of the objects annotated with #ModelAttribute. That's what Spring looks at when deciding what to do: your annotations.
Since both UserEditForm and AdminSession are annotated with #ModelAttribute and both have a User property, a request property named user.name will get bound to both User properties.
You tried to include the command name in the input name and got an error. That's because when binding occurs it occurs on your command object and Spring looks for properties on it (the bindinf path is relative to the command object) and off course the expression does not find any property with that name. If you want to use a full name you could wrap the form in another object and use that for your command instead, something like this:
public class UserEditFormWrapper {
private UserEditForm form;
public UserEditForm getForm() {
return form;
}
public void setForm(UserEditForm form) {
this.form = form;
}
}
Now you can use an expression like this in your inputs: form.user.name and when you submit to your handler method that now looks like this:
#RequestMapping
public String save(HttpServletRequest req,
#ModelAttribute("userEditForm") UserEditFormWrapper formWrapper,
BindingResult formBindingResult,
ModelMap model,
#ModelAttribute("session") AdminSession session) {
UserEditForm form = formWrapper.getForm();
// some validation etc
}
the binding won't be triggered since AdminSession does not have a form property.
That's one way to solve this but it's kind of a hack. You don't want to have the request parameters bound to AdminSession but that's part of your model so you must have created it somewhere and placed it on the model, right? If so, then remove it from the method's parameters and just get it from the model, something like:
#RequestMapping(value = "/test", method = { RequestMethod.POST })
public String handlePost(HttpServletRequest req,
#ModelAttribute("userEditForm") UserEditForm form,
BindingResult formBindingResult, ModelMap model) {
AdminSession session = (AdminSession) model.get("session");
// some validation etc
}

Force Initialization of #ModelAttributes in Spring MVC 3.1

I am writing a wizard-like controller that handles the management of a single bean across multiple views. I use #SessionAttributes to store the bean, and SessionStatus.setComplete() to terminate the session in the final call. However, if the user abandons the wizard and goes to another part of the application, I need to force Spring to re-create the #ModelAttribute when they return. For example:
#Controller
#SessionAttributes("commandBean")
#RequestMapping(value = "/order")
public class OrderController
{
#RequestMapping("/*", method=RequestMethod.GET)
public String getCustomerForm(#ModelAttribute("commandBean") Order commandBean)
{
return "customerForm";
}
#RequestMapping("/*", method=RequestMethod.GET)
public String saveCustomer(#ModelAttribute("commandBean") Order commandBean, BindingResult result)
{
[ Save the customer data ];
return "redirect:payment";
}
#RequestMapping("/payment", method=RequestMethod.GET)
public String getPaymentForm(#ModelAttribute("commandBean") Order commandBean)
{
return "paymentForm";
}
#RequestMapping("/payment", method=RequestMethod.GET)
public String savePayment(#ModelAttribute("commandBean") Order commandBean, BindingResult result)
{
[ Save the payment data ];
return "redirect:confirmation";
}
#RequestMapping("/confirmation", method=RequestMethod.GET)
public String getConfirmationForm(#ModelAttribute("commandBean") Order commandBean)
{
return "confirmationForm";
}
#RequestMapping("/confirmation", method=RequestMethod.GET)
public String saveOrder(#ModelAttribute("commandBean") Order commandBean, BindingResult result, SessionStatus status)
{
[ Save the payment data ];
status.setComplete();
return "redirect:/order";
}
#ModelAttribute("commandBean")
public Order getOrder()
{
return new Order();
}
}
If a user makes a request to the application that would trigger the "getCustomerForm" method (i.e., http://mysite.com/order), and there's already a "commandBean" session attribute, then "getOrder" is not called. I need to make sure that a new Order object is created in this circumstance. Do I just have to repopulate it manually in getCustomerForm?
Thoughts? Please let me know if I'm not making myself clear.
Yes, sounds like you may have to repopulate it manually in getCustomerForm - if an attribute is part of the #SessionAttributes and present in the session, then like you said #ModelAttribute method is not called on it.
An alternative may be to define a new controller with only getCustomerForm method along with the #ModelAttribute method but without the #SessionAttributes on the type so that you can guarantee that #ModelAttribute method is called, and then continue with the existing #RequestMapped methods in the existing controller.

Spring MVC 3.0: How do I bind to a persistent object

I'm working with Spring MVC and I'd like it to bind a a persistent object from the database, but I cannot figure out how I can set my code to make a call to the DB before binding. For example, I'm trying to update a "BenefitType" object to the database, however, I want it to get the object fromthe database, not create a new one so I do not have to update all the fields.
#RequestMapping("/save")
public String save(#ModelAttribute("item") BenefitType benefitType, BindingResult result)
{
...check for errors
...save, etc.
}
There are several options:
In the simpliest case when your object has only simple properties you can bind all its properties to the form fields (hidden if necessary), and get a fully bound object after submit. Complex properties also can be bound to the form fields using PropertyEditors.
You may also use session to store your object between GET and POST requests. Spring 3 faciliates this approach with #SessionAttributes annotation (from the Petclinic sample):
#Controller
#RequestMapping("/owners/*/pets/{petId}/edit")
#SessionAttributes("pet") // Specify attributes to be stored in the session
public class EditPetForm {
...
#InitBinder
public void setAllowedFields(WebDataBinder dataBinder) {
// Disallow binding of sensitive fields - user can't override
// values from the session
dataBinder.setDisallowedFields("id");
}
#RequestMapping(method = RequestMethod.GET)
public String setupForm(#PathVariable("petId") int petId, Model model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet); // Put attribute into session
return "pets/form";
}
#RequestMapping(method = { RequestMethod.PUT, RequestMethod.POST })
public String processSubmit(#ModelAttribute("pet") Pet pet,
BindingResult result, SessionStatus status) {
new PetValidator().validate(pet, result);
if (result.hasErrors()) {
return "pets/form";
} else {
this.clinic.storePet(pet);
// Clean the session attribute after successful submit
status.setComplete();
return "redirect:/owners/" + pet.getOwner().getId();
}
}
}
However this approach may cause problems if several instances of the form are open simultaneously in the same session.
So, the most reliable approach for the complex cases is to create a separate object for storing form fields and merge changes from that object into persistent object manually.
So I ended up resolving this by annotating a method with a #ModelAttribute of the same name in the class. Spring builds the model first before executing the request mapping:
#ModelAttribute("item")
BenefitType getBenefitType(#RequestParam("id") String id) {
// return benefit type
}
While it is possible that your domain model is so simple that you can bind UI objects directly to data model objects, it is more likely that this is not so, in which case I would highly recommend you design a class specifically for form binding, then translate between it and domain objects in your controller.
I'm a little confused. I think you're actually talking about an update workflow?
You need two #RequestMappings, one for GET and one for POST:
#RequestMapping(value="/update/{id}", method=RequestMethod.GET)
public String getSave(ModelMap model, #PathVariable Long id)
{
model.putAttribute("item", benefitDao.findById(id));
return "view";
}
then on the POST actually update the field.
In you example above, your #ModelAttribute should already be populated with a method like the above method, and the properties be bound using something like JSTL or Spring tabglibs in conjunction with the form backing object.
You may also want to look at InitBinder depending on your use case.

Resources