0

I've got some code which looks like this

@Controller
public class FooController {

@RequestMapping(value = "/foos", method = POST)
public String createFoo(@ModelAttribute("FooDto")
    @Valid FooDto fooDto, BindingResult bindingResult, Model model) {

    var foo = fooMapper.toFooModel(fooDto);

    if (bindingResult.hasErrors()) {
        var fooDto1 = fooMapper.toFooDto(foo);

        model.addAttribute("FooDto", fooDto1); // binding result disappears
        ...
    }

    ...

Also some Test for my Controller class which looks like the following:

@Test
void createFooWithValidationError() throws Exception {

    perform(post("/foos")).andExpect(status().isOk())
        .andExpect(model().attribute("foo", notNullValue()))
        .andExpect(model().errorCount(1)); // this fails, no error/binding result exists!
}

The test has not been successfull, because there is no binding result after setting

model.addAttribute("FooDto", fooDto1);.

I know, that i could set

model.addAttribute("FooDto", fooDto);

whereby the binding result does not disappear.

However I wonder why the binding result is disappearing. Is there some spring magic which binds the binding result to the concrete instance of my model attribute? Is there some other workaround to make the above code working?

Theiaz
  • 568
  • 6
  • 24
  • Why would you need to even add the object again to the model? It should be the same. – M. Deinum Feb 08 '22 at 07:50
  • I know the above code is not best practice. However I stumbled upon this code and now i want to know if I am right with my assumption. – Theiaz Feb 08 '22 at 07:55
  • The problem is that you actually have 2 models here which are merged (the `BindingResult` also has a `Model`. If you do stuff in 1 it affects the other and vice versa. Next to that you just shouldn't be doing this as the model object is already add to the model (and you are basically interfering with that in this way). – M. Deinum Feb 08 '22 at 07:57
  • Thanks for the explanation. I dived into the spring code and explored the solution. I'll add it as my answer – Theiaz Feb 08 '22 at 08:23

1 Answers1

0

I'll dived into the spring code and found my answer:

model.addAttribute("FooDto", fooDto1); does call

public ModelMap addAttribute(String attributeName, @Nullable Object attributeValue) {
    Assert.notNull(attributeName, "Model attribute name must not be null");
    put(attributeName, attributeValue); // calls BindingAwareModelMap
    return this;
}
public class BindingAwareModelMap  {

   public Object put(String key, @Nullable Object value) {
       removeBindingResultIfNecessary(key, value); 
       return super.put(key, value);
   }

   private void removeBindingResultIfNecessary(Object key, @Nullable Object value) {
        ...
        if (bindingResult != null && bindingResult.getTarget() != value) {
            remove(bindingResultKey); // Removes the binding result if target refernces to other instance
        }
    }
}

As u can see if the binding results references to an other instance the binding result entry in ModelMap is removed.

Theiaz
  • 568
  • 6
  • 24