BindingResult inserted before rendering

Issue: SPR-14542
This commit is contained in:
Rossen Stoyanchev 2016-11-07 11:58:17 +02:00
parent ae003e89c1
commit e59dcedfee
2 changed files with 42 additions and 3 deletions

View File

@ -17,6 +17,7 @@
package org.springframework.web.reactive.result.view; package org.springframework.web.reactive.result.view;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -38,11 +39,14 @@ import org.springframework.http.MediaType;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebExchangeDataBinder;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.reactive.HandlerResult; import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.HandlerResultHandler; import org.springframework.web.reactive.HandlerResultHandler;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver; import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.result.AbstractHandlerResultHandler; import org.springframework.web.reactive.result.AbstractHandlerResultHandler;
import org.springframework.web.reactive.result.method.BindingContext;
import org.springframework.web.server.NotAcceptableStatusException; import org.springframework.web.server.NotAcceptableStatusException;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.HttpRequestPathHelper; import org.springframework.web.util.HttpRequestPathHelper;
@ -241,6 +245,7 @@ public class ViewResolutionResultHandler extends AbstractHandlerResultHandler
} }
return resolveAsyncAttributes(model.asMap()) return resolveAsyncAttributes(model.asMap())
.doOnSuccess(aVoid -> addBindingResult(result, exchange))
.then(viewsMono) .then(viewsMono)
.then(views -> render(views, model.asMap(), exchange)); .then(views -> render(views, model.asMap(), exchange));
}); });
@ -322,6 +327,24 @@ public class ViewResolutionResultHandler extends AbstractHandlerResultHandler
.then(); .then();
} }
private void addBindingResult(HandlerResult result, ServerWebExchange exchange) {
BindingContext context = result.getBindingContext();
Map<String, Object> model = context.getModel().asMap();
model.keySet().stream()
.filter(name -> isBindingCandidate(name, model.get(name)))
.filter(name -> !model.containsKey(BindingResult.MODEL_KEY_PREFIX + name))
.forEach(name -> {
WebExchangeDataBinder binder = context.createDataBinder(exchange, model.get(name), name);
model.put(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
});
}
private boolean isBindingCandidate(String name, Object value) {
return !name.startsWith(BindingResult.MODEL_KEY_PREFIX) && value != null &&
!value.getClass().isArray() && !(value instanceof Collection) &&
!(value instanceof Map) && !BeanUtils.isSimpleValueType(value.getClass());
}
private Mono<? extends Void> render(List<View> views, Map<String, Object> model, private Mono<? extends Void> render(List<View> views, Map<String, Object> model,
ServerWebExchange exchange) { ServerWebExchange exchange) {

View File

@ -166,7 +166,12 @@ public class ViewResolutionResultHandlerTests {
returnType = forClass(TestBean.class); returnType = forClass(TestBean.class);
returnValue = new TestBean("Joe"); returnValue = new TestBean("Joe");
String responseBody = "account: {id=123, testBean=TestBean[name=Joe]}"; String responseBody = "account: {" +
"id=123, " +
"org.springframework.validation.BindingResult.testBean=" +
"org.springframework.validation.BeanPropertyBindingResult: 0 errors, " +
"testBean=TestBean[name=Joe]" +
"}";
testHandle("/account", returnType, returnValue, responseBody, resolver); testHandle("/account", returnType, returnValue, responseBody, resolver);
testHandle("/account", resolvableMethod().annotated(ModelAttribute.class), testHandle("/account", resolvableMethod().annotated(ModelAttribute.class),
@ -239,7 +244,11 @@ public class ViewResolutionResultHandlerTests {
.block(Duration.ofSeconds(5)); .block(Duration.ofSeconds(5));
assertEquals(APPLICATION_JSON, this.response.getHeaders().getContentType()); assertEquals(APPLICATION_JSON, this.response.getHeaders().getContentType());
assertResponseBody("jsonView: {testBean=TestBean[name=Joe]}"); assertResponseBody("jsonView: {" +
"org.springframework.validation.BindingResult.testBean=" +
"org.springframework.validation.BeanPropertyBindingResult: 0 errors, " +
"testBean=TestBean[name=Joe]" +
"}");
} }
@Test @Test
@ -272,7 +281,14 @@ public class ViewResolutionResultHandlerTests {
this.request.setUri("/account"); this.request.setUri("/account");
handler.handleResult(this.exchange, result).blockMillis(5000); handler.handleResult(this.exchange, result).blockMillis(5000);
assertResponseBody("account: {bean1=TestBean[name=Bean1], bean2=TestBean[name=Bean2]}"); assertResponseBody("account: {" +
"bean1=TestBean[name=Bean1], " +
"bean2=TestBean[name=Bean2], " +
"org.springframework.validation.BindingResult.bean1=" +
"org.springframework.validation.BeanPropertyBindingResult: 0 errors, " +
"org.springframework.validation.BindingResult.bean2=" +
"org.springframework.validation.BeanPropertyBindingResult: 0 errors" +
"}");
} }