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.
Related
#Controller
public class GreetingController {
#GetMapping("/greeting")
public String greeting(HttpServletRequest request, Model model) {
String name = request.getParameter("name");
model.addAttribute("name", name);
return "greeting";
}
}
What does the Model do? Is this essentially the context variables that are passed to the "greeting.html" file? Is the only use of it addAttributes that will then be passed to the HTML template? Where could I read more about what Model does and how it should be used?
Did you see the documentation of Model?
Java-5-specific interface that defines a holder for model attributes. Primarily designed for adding attributes to the model. Allows for accessing the overall model as a java.util.Map.
Model is an essential part of MVC pattern which is widely used in Spring. As you have said, a Model is a holder of the context data passed by a Controller to be displayed on a View.
You can use only one Model which contains more data distinct with a unique key because the Model is based on the java.util.Map - as the documentation says..
Well, yes, that's what the Model do. But you can merge two Model objects parameters too, so this makes things easier.
More than it, you use ModelMap and ModelandView objects. If you want to read about it, take a look at: Spring MVC Model Map Model
In this case the model is used to pass data to the view. For example you are using JSP, or some template like thymeleaf, you can put thigns that you want to be displayed in the model, and you will be able to access it from those views.
You could read more about that in the Spring MVC Reference
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 am building an application with simple Servlets and the MVC pattern. I am not using any framework like Spring.
I have some Model classes like this:
public class BlogPost {
private List<Comment> _comments;
// Things
}
and
public class Comment {
// Code
}
Posts can have zero or more comments associated with them in that collection.
However, I want to attach some additional information to the BlogPost Model before it is passed to the View, namely a value I set in a Cookie once a user makes a comment on a BlogPost. Strictly speaking, this is not a part of the BlogPost Model itself -- it is unrelated, incidental information, however I am not sure if I should make it easy on myself and just add it to the BlogPost class or do something to abstract this out a bit more.
So, should I add a field to the BlogPost class to handle this additional value, OR should I make a "View Model" along the lines of this which gets passed to the JSP view:
public class BlogPostView {
public BlogPostView(BlogPost bp, String message) {
// Constructor stuff, save these to instance variables
}
public BlogPost getBlogPost() { /* ... */ }
public String getMessage() { /* ... */ }
}
If BlogPost and your cookie data are unrelated, it is a bad idea to put the cookie data in your BlogPost class. The BlogPost class should represent what it's called - a blog post. It would be confusing to have other data associated.
Your second option of creating a class specifically to pass to the view is a better idea, though I'm curious to know why you need to pass the blog post and the cookie data as one object to your view? If you're using raw servlets:
request.setAttribute("blogPost",blogPost);
request.setAttribute("cookieData",cookieData);
Using a model class (e.g. Spring MVC ModelMap):
model.addAttribute("blogPost",blogPost);
model.addAttribute("cookieData",cookieData);
Your view will have access to both pieces of data, which you can manipulate using JSTL or other tag libraries.
If there's something I'm missing, can you elaborate more?
Create a HashMap model - and pass it along with the response to view.
model.put("blog", blog)
model.put("message", "some message")
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
Using spring MVC, how would I create a form that doesn't map to a entity (i.e. it has properties from multiple entities).
I want to also validate and use their 'results' object that has the errors collection etc.
Any online examples of this? I need to see it to understand it (newbie)
You'd simply create a new class containing all the properties you need for the form and use this as model attribute for your form. On the receiving call you can then use this type, too. Spring will automatically bind properties for you. You should also consider using JSR-303 validation annotations.
The general approach is to load all the necessary entities to create the form backing object from on the GET request. Then you put that form backing object in the model. On the POST/PUT request you have to reconstruct the actual entities touched. Typically you load them again then and apply the new submitted partial data to them.
In general it might be a good idea to construct a dedicated component to handle that assembling behaviour for you to not pollute the controller with that code.
/**
* Prepares the form by setting up a custom form backing object.
*/
#RequestMapping(value = "account/{id}", method = GET)
public String processGet(#PathVariable("id") Long id, Model model) {
Account account = accountService.getAccount(id);
return prepareForm(dtoAssembler.createFormFor(account), model);
}
/**
* Process form submission. Uses JSR-303 validation and explicit error
* handling.
*/
#RequestMapping(value = "account/{id}", method = PUT)
public String processGet(#ModelAttribute("accountForm") #Valid AccountForm form, Errors errors, Model model) {
if (errors.hasErrors()) {
return prepareForm(form, model);
}
Account account = accountService.getAccount(form.getId());
accountService.save(dtoAssembler.applyData(account, form));
return "redirect:/accounts";
}
/**
* Populates the given {#code Model} with the given {#code AccountForm}
* and returns the view to show the form.
*/
private String prepareForm(AccountForm form, Model model) {
model.addAttribute("accountForm", form);
return "account";
}
I just wrote it this way here to emphasize what's going on. In a real world scenario I'd probably let the DtoAssembler do all the work with the service (so I'd inject the service into the assembler).
To ease transferring data from the DTO into the domain object using Dozer, BeanUtils or something similar is probably reasonable.