Does a variable with #ModelAttribute get populated from request parameters? - spring

I am interested in the specifics of Spring #ModelAttribute's work on method parameters.
As we know, when a requested attribute is absent from the model, then its instance gets created and populated from the view form.
My question concerns the scenario when the form does not have expected properties but when such properties are available in URL template parameters.
I would like to know if in this case our variable will be populated with the values of those request parameters?
Like here, for instance, will the variable attributeToPopulate get populated with the parameters 1,2,3 from the URL http://localhost:8080/MyApp/parameter1=whatever?parameter2=whatever?parameter3=whatever?:
RequestMapping(method = RequestMethod.GET)
public String fooMethod(#ModelAttribute("attributeName") FooClass attributeToPopulate){
// method implementation
return "view";
}
Neither Spring documentation, reference documentation, nor Q&A sites refer to such situations explicitly. However, one post here on Stack Overflow does mention that variables annotated with ModelAtrribute get populated in this way (answer of the user Xelian):
name="Dmitrij"&countries=Lesoto&sponsor.organization="SilkRoad"&authorizedFunds=&authorizedHours=&
Considering a small number of upvotes for that answer, I am bit skeptical but at the same time curious about whether #ModelAttribute indeed functions in such way.
Any informative input will be greatly appreciated.

You can set default values to fields in the FooClass instead of setting them in RequestMapping annotation. In this case you will not have to copypaste RequestMapping with default values in all the methods where you are working with FooClass as a ModelAttribute.
#tomatefraiche I have tried to do this by
<spring:url value="/hello?name=default" var="userActionUrl" />
<form:form method="get" modelAttribute="user" action="${userActionUrl}">
<form:input path="name" type="text" disabled="true" />
</form:form>
and
#GetMapping("/hello")
public String hello(#ModelAttribute("user") User user, Model model) {
model.addAttribute("name", user.name);
model.addAttribute("user", user);
return "hello";
}
and looks like Spring override values in the url. In the controller there is an empty value. Also URL is hello?name= after form submission. So looks like you can not set default values through url since it will be replaced.

Related

passing a model to the next controller from view

I have a form with few inputs, name, email, message and some checkboxes. I've created a model for these inputs and set all the validations i require.
But now I also want to pass my model (i.e. from #model MyModel) or rather some object property of my model together with those inputs.
Is populating a VewBag/viewData with my model a way to go?
#{
ViewBag.MyModel = Model;
// or ViewBag.ThatProperty = Model.ThatProperty
}
or do i still have a better way up my sleeve?
ViewBag and ViewData persist in one trip from server to client, and not the other way around.
There is no way to pass an object from the view to the controller. If it's a database object, you can pass the object Id using one of the two methods described below, then query the DB on post.
If you have no other way, you can encode the object as a JSON string (using the Newtonsoft package, for example) and pass it also using one of the two methods described below, but this isn't the best option.
To pass a property from the View to the Controller, you have two options:
Url Parameter
Hidden field
Url Parameter
<form ... asp-route-ThatProperty="#Model.ThatProperty">
...
</form>
Form Field
<form>
<input type="hidden" name="ThatProperty" value="#Model.ThatProperty" />
</form>
Controller Action
If 'ThatProperty' doesn't exist on your model, receive it as an extra parameter.
public IActionResult MyAction (MyModel model, string ThatProperty)
{
...
}

Spring Boot Post Request Method not having all Object values

Using Spring Boot, Hibernate JPA and Thymeleaf.
I have an Order database table which currently only holds 1 record. This record has a few columns and some of the columns are not seen on any forms, they are set upon saving the Order, for instance the creation date.
On the GET request below I select the specific Order and all values are returned into the Order object as expected.
This is my GET Request method:
#RequestMapping(value = "/editorder/{orderId}", method = RequestMethod.GET)
public String editOrderGet(Model model, #PathVariable long orderId)
{
Order order = orderService.findById(orderId);
model.addAttribute("order", order);
return "/editorder";
}
This is a small snippit of my edit order html form using Thymeleaf, binding the Order object to the form using th:object as below:
<form role="form" th:action="#{/editorder}" th:object="${order}" method="post">
<input type="hidden" th:field="*{orderId}"/>
<button type="submit" class="btn btn-primary">Update Order</button>
.
.
</form>
And this is my POST Request method:
#RequestMapping(value = "/editorder", method = RequestMethod.POST)
public String editOrderPost(Model model,
#Valid #ModelAttribute("order") Order order, BindingResult bindingResult)
{
//rest of code here
}
As you can see, on the GET request I am adding the Order object to the model.
On the html form, I am binding the Order object to the entire form. Then on the POST request I am getting the Order object.
But on the POST it is seen as a new Order and only contains the fields as specified in the form, it does for instance not contain the creation date as seen in the GET request.
My question is this:
Am I missing something or do I explicitly need to go set each of those fields as hidden fields on my form?
In your GET response you may be returning the whole Order object into the Model, but Thymeleaf when trying to build the actual html from template will pick only the items it needs to build the template. So only the fields that are used in the form are used to build the form in your html page.
So when u resubmit the form to the POST service only those fields that are available in the form is reposted.
If u want these fields to be displayed on the page then add these fields in the Form. Thymeleaf picks them and displays in the form. If you dont want them to be shown in the Page then just ignore them. The Order object which u receive in the POST would not have that fields as they were not available in original form.
U can get them by querying the database, any how you do have the order id saved as the Hidden field.
public String editOrderPost(Model model,
#Valid #ModelAttribute("order") Order order, BindingResult bindingResult){
Order orderFromDB = orderService.findById(order.getId());
// Code to update the orderFromDB from order object
orderService.save(order);
}
This will save the updated fields to the database.
Generally its not a good practice to expose the Entity objects to the API. Try using a DTO/value object. This can have only fields that define your business fields. Also u can use BeanMapper frameworks like dozer/mapstruct/modelmapper to copy from DTO to Entity and vice versa.

How does Spring bind form values into class variables?

If I use a form:form object in Spring, I can use the commandName in order to let Spring inject the class variable values.
However, I wonder, how does the controller catch this value?
#RequestMapping(method = RequestMethod.POST, value = "/form")
public String postForm(#ModelAttribute("item") Item item, ModelMap model)
{
return "result";
}
In the above code, the Item is injected. However, even changing the name of this variable (or removing the modelattribute), doesn't affect that this variable is injected with the form values.
Will spring just inject the values in the first model class found, from the form? How does Spring know that it has to inject the form into the Item item parameter?
At first I thought the variable in the controller (POST) should have the name of commandName of the form, but it does work with other names as well, strangely enough.
There is a dedicated section in the Spring Documentation describing the usage of #ModelAttribute on method arguments.
This process is known as Data Binding on submit and is following some conventions:
If #ModelAttribute is explicitely declared with a name on an argument (your case). In this case the submitted data of the form are copied over automatically under this name. You can check yourself that it is already there in your ModelMap model by invoking/inspecting model.get("item").
If there is no #ModelAttribute argument annotation at all, then the attribute name is assumed from the type essentially in your case type Item converts to attribute name item (camelCase notation) that is created for you holding a new Item with the form data-bind'ed fields. That is also there in the ModelMap (same check as above: model.get("item"))
Key point to realise is in all these cases DataBinding occurs before hitting your Post form RequestMapping.

Spring: Registration form remove object from model

In my Spring MVC Project I created a registration page.In this page there is a form in which the user insert his information(name,surname and so on).I have used the Spring tag form to bind the object "cliente" to the form. In my controller I have:
#RequestMapping(value="/registration",method=RequestMethod.GET)
public String viewRegistration(ModelMap model){
model.addAttribute("cliente",clienteFactory.createCliente());
return "registrazione";
}//registrazione
In registration.jsp
<form:form method="post" action="add" modelAttribute="cliente">
....
</form:form>
In this project I have not used Spring Security,because I'm a student and I hadn't learned this part yet.
If the user leaves the page without register, I want to delete the object "Cliente" from the model.How can I solve it?Thanks
At first you don't need to delete object from model as if client leaves page with mapping /registration model ref will be overriden with model of another mapping method.
Second in more cases it's no good idea to call your method clienteFactory.createCliente()) in GET request method. Better to call it in POST after user fill all form fields and post his request than you know that you need to call clientFactory.Also use #ModelAttribute annotation as method argument.
As you have in your form form:form method="post" it will not working without such method
#RequestMapping(value="/registration",method=RequestMethod.POST)
public String makeRegistration(ModelMap model){
.....
Also see simple Sring tutorial for handling forms.

Can #RequestParam be used on non GET requests?

Spring documentation says:
Use the #RequestParam annotation to bind request parameters to a
method parameter in your controller.
AFAIK, request parameters are variables retrieved from query strings if the request method is GET. They are also the variables retrieved from the form values when the request method is POST. I've verified this using a simple JSP that displays request parameters through method request.getParameter("key").
But it seems to me that #RequestParam only works on GET method requests. It can only get values from query strings.
Is this a bug in the documentation? Can someone please cite me some documentation that describes exactly what #RequestParam is used for, what it cannot be used for, and how it gets populated?
Can I use #RequestParam for POST methods to get the form values? If I can't use #RequestParam, what else can I use? I'm trying to avoid calling request.getParameter("key").
It works with posts too. Can you post your method body and you html?
Yes it works perfectly with post method too. you can mention the method attribute of #RequestParam as RequestMethod=POST. Here is the code snippet
#RequestMapping(value="/register",method = RequestMethod.POST)
public void doRegister
(
#RequestParam("fname") String firstName,
#RequestParam("lname")String lastName,
#RequestParam("email")String email,
#RequestParam("password")String password
)
Instead of #RequestParam which binds to a single form value, you can use #ModelAttribute annotation and bind to the whole object. But it should be used in conjunction with form or bind Spring's JSTL.
Example:
- controller that calls JSP-page, it should add objects to a Model:
#RequestMapping(value="/uploadForm", method=RequestMethod.GET)
public String showUploadForm(Model model) {
Artist artist = new Artist();
Track track = new Track();
model.addAttribute("artist", artist);
model.addAttribute("track", track);
return "uploadForm";
}
JSP might look something like that:
Track Title *:
Controller that processes form submission;
#RequestMapping(value="/uploadToServer", method=RequestMethod.POST)
public String uploadToServer(#ModelAttribute("artist") Artist artist, #ModelAttribute("track") Track track) { .... }
Here I found a good explanation of using #ModelAttribute annotation - krams915.blogspot.ca

Resources