Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Model property is set to null by thymeleaf

I have been dealing with the following issue:

I have a simple model class called User.

public class User{

  private Long id;
  private String name;
  ...}

And this is my the controller code:

@RequestMapping ( value = "/users", params = { "id" } )
public String showEditForm( final Model model, final HttpServletRequest req )
{
    User edit = this.userRepository.findOne( Long.valueOf( req.getParameter( "id" ) ) );
    model.addAttribute( "user", edit );
    return "edit-user";
}


@RequestMapping ( value = "/users", method = RequestMethod.POST, params = {"edit"     
}) 
public String editUser( @Valid @ModelAttribute ( "user" ) final User user,
final BindingResult bindingResult, final Model model )
{
   if ( bindingResult.hasErrors() )
    {
        return "edit-user";
    }

    return "redirect:/users";
}

The following is the code snippet to displaying all the users:

<div class="userlist" th:unless="${#lists.isEmpty(users)}">
    <h2 th:text="#{list.user.title}"></h2>
    <table>
        <thead>
            <tr>
                <th th:text="#{name}" />
                <th th:text="#{details}" />                 
            </tr>
        </thead>
        <tbody>
            <tr th:each="u : ${users}">
                <td th:text="${u.name}" />                  
                <td><a th:href="@{/users(id=${tc.id})}" th:text="#{edit}"></a></td>
            </tr>
        </tbody>
    </table>
</div>

And eventually the submit form:

<form action="#" th:action="@{/users}" th:object="${user}"
        method="post">
        <fieldset>
            <ul th:if="${#fields.hasErrors('*')}" class="errorlist">
                <li th:each="err : ${#fields.errors('*')}" th:text="${err}"></li>
            </ul>
            <div>
                <input type="text" th:field="*{id}"/>
            </div>
            <div>
                <label for="name"> <span th:text="#{name}"></span>
                </label> <input type="text" th:field="*{name}"
                    th:class=" ${#fields.hasErrors('name')}? 'fieldError'" />
            </div>          
            <div class="submit">
                <button type="submit" th:text="#{update}" name="edit"></button>                 
            </div>
        </fieldset>
    </form>

And now the problem description: As long as the 'id'-field is present within the submit form, everything works fine. If I remove the 'id'-field from the submit form though, because the id property isn't meant to be modified, the workflow doesn't work anymore. In fact, the id is null in the editUser() method. I assume that Thymeleaf does set the value of the id property to null if it is not present within the submit form. But I'm not sure about this. And I think there must be some solution to this problem other than having to let the id-property reside in the submit form.

I hope anyone can help here out.

Thanks.

Edmond

like image 304
edmond Avatar asked Dec 19 '22 22:12

edmond


1 Answers

That has nothing to do with Thymeleaf, but how binding works. Spring will only bind attributes to the model object which are present as parameter in the request. If you remove id that isn't present and as such cannot be bound (what should it be bound to?).

A solution to this is to either specify the id as a hidden form, so that it is present. Or in between requests store your object in the session (using @SessionAttributes on the controller) that way the earlier retrieved object is going to be used for binding.

@Controller
@SessionAttributes("user")
public class UserController {

    @ModelAttribute("user")
    public User initUser(@RequestParam("id") Long id) {
        return this.userRepository.findOne(id);
    }

    RequestMapping ( value = "/users", params = { "id" } )
    public String showEditForm() {
        return "edit-user";
    }

    @RequestMapping ( value = "/users", method = RequestMethod.POST, params = {"edit"}) 
    public String editUser( @Valid @ModelAttribute ( "user" ) final User user, final BindingResult bindingResult, SessionStatus status;) {
       if ( bindingResult.hasErrors() ) {
            return "edit-user";
        }
        status.setComplete(); // Indicate we are done,so @SessionAttributes can be cleaned up
        return "redirect:/users";
    }

Something like that should preserve the User between sessions, and the SessionStatus can then be used to trigger the cleanup of the session attributes.

like image 152
M. Deinum Avatar answered Jan 07 '23 15:01

M. Deinum