Using Spring/SpringMVC 3.0.5 I've defined a method in my controller like this:
#RequestMapping(params = { "save","!delete" }, method = RequestMethod.POST)
public ModelAndView saveFoo(...
#ModelAttribute("vo") #Valid DescriptionBuilderVO vo, BindingResult result) {
...
result.rejectValue("foo.sequenceNumber", "foo.builder", new Object[]{vo.getFoo().getSequenceNumber()}, "Sequence Number too high"); vo.getFoo().setSequenceNumber(originalNumber);
return new ModelAndView(WebConstants.VIEW_BUILDER, "vo", vo);
Notice that I'm attempting to set a value in the VO object inside the controller. The funny thing is that if I do this with #ModelAttribute the new value doesn't show up. If I remove #ModelAttribute from the method contract, the new value appears exactly as you would think. The problem comes when there are errors, the only way to get the errors is to have the #ModelAttribute in the contract.
BTW my HTML looks like:
HTML
<form:input path="foo.sequenceNumber" id="sequenceNumber" size="4" maxlength="4"/>
<form:errors path="foo.sequenceNumber" cssClass="ui-state-error" />
foo.sequenceNumber = the value the user typed in; when I use #ModelAttribute
foo.sequenceNumber = the value I set in the controller; but I lose any errors
It seems to me that SpringMVC is putting the ModelAttribute VO into a "special" place and passing it back to the jsp but not in an obvious location. Does anyone know how I can get at the VO object in this situation?
wierd. The same thing works for me. The only difference i see is the order of Valid and ModelAttribute
can you try reversing the order of Valid and ModelAttribute?
public ModelAndView saveFoo(...
#Valid #ModelAttribute("vo") DescriptionBuilderVO vo, BindingResult result) {
}
BTW, which version of spring are you using?
I've tried many different things including the reordering the Valid and ModelAttribute annotations and it doesn't seem to make a difference.
In reading the documentation suggested by Pat in the comments it does refer to a special context where the VO is stored. Anyway, if you're trying anything similar to this I suggest you go about it in a different way perhaps building a completely new VO and passing it back to the view, which is what I did.
Try naming your object "descriptionBuilderVO", like the following:
#Valid #ModelAttribute("descriptionBuilderVO") DescriptionBuilderVO descriptionBuilderVO,
BindingResult result)
I know it shouldn't be this way, but I've found problems when the name of the object is different than the class name.
Note that you'll also have to change the name of the object in your jsp:
<form:form commandName="descriptionBuilderVO"
...etc...
Related
From the thymeleaf guide, I have seen the following code snippet:
<ul th:if="${#fields.hasErrors('global')}">
<li th:each="err : ${#fields.errors('global')}" th:text="${err}">Input is incorrect</li>
</ul>
So, I m thinking if there is a way that I can pass error message from the Spring MVC controller and display the error somewhere on the page using the above code.
I have seen some online resources using BindingResult on the method signature as below.
bindingResult.rejectValue("username", "username.notvalid", "Username is not valid");
But what if the controller does not validate any objects? Will I still be able to use bindingResult?
I think I can also add error message as key pair into the model as below.
model.addAttributes("error","error message");
But, I want to know if there is some standard way of handling error in the Spring controller.
Should I use exception handling instead? But how can I pass error message back to the view?
Thanks
Updated use case:
For example, in method A, an error was detected as below.
public String methodA (Model m, RedirectAttributes model){
if (error == true){
model.addFlashAttribute("error.message","String");
return "redirect:MethodB";
}
}
public String methodB(Model m){
if(m.containsAttribute("error.message"){
//Way to pass the error to view page for display.
}
}
Should the error be handled in such way? The reason it goes through the second action is because there are certain data needed to be displayed on the final view page.
Do not use exceptions for validation errors, use BindingResult, give a look to this question and javadoc.
You can also create an implementation like MapBindingResult, use it and set it in the request/model for the view.
A BindingResult is a standard Spring data structure that can be used to hold errors, is usually used when there could be more than one error or if you have view code for it; if you just have one single error you could also just set it in the model.
Since you use flash attributes, in methodB the error is already in the model, so it will be available to the view. See also Spring MVC: Passing a Model as an argument to a controller method VS instantiating it explicitly
public String methodB(Model m){
...
return "myview";
}
Note that if you use a dot in attribute key (like error.message) you should use the right syntax in the view like, for JSP: model["error.message"] and not model.error.message
I have done a few MVC controllers now and used the spring form tags to pass data back and forth but I realise now my actual understanding is a little thin. In my current case I could actually just send the response as url parameters but there are about 15 and I would prefer to send it as a pojo if possible.
My actual question... is ... is it possible to set up a spring style model attribute in a jsp without the attribute having been passed in and without using the form tags ?
So for example something along the lines of
//Pojo
Class personclass
{
private String name + getters and setters
private String address + getters and setters
private String phone + getters and setters
...
}
////first mvc call
#RequestMapping ("/")
Public ModelAndView LandingPage()
{
// no mention of Person pbject
Return mandvobject;
}
//jsp page
//This is the question!
SET ModelAttribute that wasn't passed in to the page
personclass = X
//New MVC call without a submit
window.open ("/NewMVCCall")
//New mvc call
#RequestMapping ("/NewMVCCall")
Public void newMVCPage(#ModelAttribute ("pc") personclass pc, Model model)
{
//process pc object
}
Or am I missing the point and I would have to send it as a json string parameter? Sorry my grasp of this is pretty rudimentary and I'm not sure whether I could quite easily set my own http form content or whether it is because I have used Spring form objects so far that I haven't grasped the complexity of what is going on behind the scenes (i.e form tags converting pojos to json and so on) ?
Many thanks if anyone has the time to set me on the right path...
I am not sure if I am understood your question correctly but you can link a Model to your controller without having to manually pass it to a the view every time you need it, spring will take care of that:
in your Controller :
public class MyController{
#ModelAttribute("pc")
public PersonneClass getPersonnelClass(){
return new PersonneClass();
}
#RequestMapping ("/NewMVCCall")
Public void newMVCPage(#ModelAttribute ("pc") personclass pc, Model model)
{
//process pc object
}
//other methods
}
It is a good practice to stick to java conventions when naming classes so
(personneClass ) must start with an uppercase (PersonneClass) .
I have a simple Controller that looks like this:-
#Controller
#RequestMapping(value = "/groups")
public class GroupsController {
// mapping #1
#RequestMapping(method = RequestMethod.GET)
public String main(#ModelAttribute GroupForm groupForm, Model model) {
...
}
// mapping #2
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public String changeGroup(#PathVariable Long id, #ModelAttribute GroupForm groupForm, Model model) {
...
}
// mapping #3
#RequestMapping(method = RequestMethod.POST)
public String save(#Valid #ModelAttribute GroupForm groupForm, BindingResult bindingResult, Model model) {
...
}
}
Basically, this page has the following functionalities:-
User visits main page (/groups GET).
User creates a new group (/groups POST) or selects a specific group (/groups/1 GET).
User edits an existing group (/groups/1 POST).
I understand how both GET request mappings work here. Mapping #2 is defined, otherwise (/groups/1 GET) will cause a "No mapping found" exception.
What I'm trying to understand here is why mapping #3 handles both (/groups POST) and (/groups/1 POST)? It makes sense that it should handle (/groups POST) here since the request mapping matches the URI. Why (/groups/1 POST) isn't causing a "No mapping found" exception being thrown here? In fact, it almost seems like any POST with URI beginning with /groups (ex: /groups/bla/1 POST) will also be handled by mapping #3.
Can someone provide a clear explanation of this to me? Thanks much.
CLARIFICATION
I understand the fact that I can use more appropriate methods (like GET, POST, PUT or DELETE)... or I can create yet another request mapping to handle /groups/{id} POST.
However, what I want to really know is...
.... "Why does mapping #3 handle /groups/1 POST too?"
The "closest match" reasoning don't seem to hold true because if I remove mapping #2, then I would think mapping #1 will handle /groups/1 GET, but it doesn't and it causes a "No mapping found" exception.
I'm just a little stumped here.
This is complicated, I think it is better to read the code.
In Spring 3.0 The magic is done by method public Method resolveHandlerMethod(HttpServletRequest request) of the inner class ServletHandlerMethodResolver of org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.
An instance of this class exists for every Request Controller Class, and has a field handlerMethods that contains a list of all the request methods.
But let me summarize how I understand it
Spring first checks if at least one handler method matches (this can contain false negatives)
Then it creates a map of all really matching handler methods
Then it sorts the map by request path: RequestSpecificMappingInfoComparator
and takes the first one
The sorting works this way: the RequestSpecificMappingInfoComparator first compares the path with the help of an AntPathMatcher, if two methods are equal according to this, then other metrics (like number of parameters, number of headers, etc.) are taken into account with respect to the request.
Spring tries to find the mapping which matches the closest. Hence, in your case of any POST request, the only map found for the request type is Mapping# 3.
Neither of Mapping 1 or Mapping 2 matches your request type, and hence are ignored.
May be you can try removing the Mapping #3, and see that Spring throws a runtime error since it does not find a match!
I would add a PUT mapping for /groups/{id}. I guess POST would work too but not strictly correct from a HTTP perspective.
adding #RequestMapping("/{id}", POST) should cover it?
add #PathVariable to the Long id parameter in mapping #2
I'm working on converting a legacy project to Spring (trying to adjust little as possible for now) and I'm running into a small issue with mapping/translating legacy parameters to a model attribute object. I may be completely wrong in thinking about this problem but it appears to me that to translate a parameter to a specific model attribute setter is to pass in the request parameter through a method for creating a model attribute and manually call the correct setter:
#ModelAttribute("form")
public MyForm createMyForm(#RequestParameter("legacy-param") legacy) {
MyForm myForm = new MyForm();
myForm.setNewParam(legacy);
return myForm;
}
I don't necessarily want to change the request parameter name yet since some javascript and JSPs are depending on it being named that way but is there any way to do something like this? Or is there a different way to map/translate request parameters to model attributes?
public class MyForm {
#ParameterName("legacy-param")
private String newParam;
public void setNewParam(String value) { ... }
public String getNewParam() { ... }
}
#Controller
public class MyController {
#RequestMapping("/a/url")
public String myMethod(#ModelAttribute("form") MyForm myForm, BindingResult result) { ... }
}
The way you've written that model attribute method is indeed odd. I'm not entirely clear what you're actually trying to do.Assuming there are many parameters, you're going to end up with an awful lot of instances of MyForm in your ModelMap. A more 'normal' way to create model attribute would be like this:
#ModelAttribute("legacyParamNotCamel")
public MyForm createMyForm(#RequestParameter("legacy-param-not-camel") String legacy) {
return legacy;
}
Then in the JSP you can refer to it directly in expression language. e.g.,
<c:out value="${legacyParamNotCamel}"/>
If you want to put them onto a form backing object, you need to do it all in a single method that creates the object, not make new copies of it in each method. (assuming your form has more than a single parameter associated with it.)
--
It seems like what you're really trying to do though is translate the parameter names in the request before the web data binder gets ahold of it, so that you can bind oddly named parameters onto a java bean? For that you'll need to use an interceptor that translates the names before the binding process begins, or make your own subclass of the databinder than can take a property name translation map.
You placed the #ModelAttribute at the Method Level but the intention seems to be more of a formBackingObject hence we should be dealing at the Method Parameter Level
There's a difference.
I put up an explanation here on my blog along examples at Spring 3 MVC: Using #ModelAttribute in Your JSPs at http://krams915.blogspot.com/2010/12/spring-3-mvc-using-modelattribute-in.html
My task is - to create a model attribute by given request parameters, to validate it (in same method) and to give it whole to the View.
I was given this example code:
#Controller
class PromotionController {
#RequestMapping("promo")
public String showPromotion(#RequestParam String someRequestParam, Model model) {
//Create the model attribute by request parameters
Promotion promotion = Promotions.get(someRequestParam);
//Add the attribute to the model
model.addAttribute("promotion", promotion);
if (!promotion.validate()) {
BindingResult errors = new BeanPropertyBindingResult(promotion, "promotion");
errors.reject("promotion.invalid");
//TODO: This is the part I don't like
model.put(BindingResult.MODEL_KEY_PREFIX + "promotion", errors);
}
return
}
}
This thing sure works, but that part with creating key with MODEL_KEY_PREFIX and attribute name looks very hackish and not a Spring style to me. Is there a way to make the same thing prettier?
Skaffman answered the question but disappeared, so I will answer it for him.
The binding validation thing is there to bind and validate parameters, not arbitrary business objects.
That means, that if I need to do some custom validation of some general data that was not submitted by the user - I need to add some custom variable to hold that status and not use BindingResult.
This answers all the questions I had with BindingResult, as I thought it had to be used as a container for any kind of errors.
Again, thanks #Skaffman.