Modify and return ModelAttribute object if validation fails - spring

I have simple entity ...
#Entity
public class WatchedDirectory {
#Column(nullable=false)
#NotBlank(message="Filesystem path CANNOT be empty")
String filesystemPath;
}
... and GET endpoint for creating one ...
#GetMapping("/add")
public String add(#ModelAttribute WatchedDirectory watchedDirectory) {
return "mng-dir-add";
}
... that shows form made in Thymeleaf, with error validation and all. Once you hit sumbmit button data goes into POST endpoint ...
#PostMapping("/add")
public String addExecute(#Valid #ModelAttribute WatchedDirectory watchedDirectory, BindingResult result, RedirectAttributes redirect, Model model) {
if(result.hasErrors()) {
// here I want to iterate through
// errors and clean erroneous fields
return "mng-dir-add";
}
watchedDirectory = fs.persistDirectory(watchedDirectory);
redirect.addFlashAttribute("added", watchedDirectory);
return "redirect:/list";
}
... and everything is nice and dandy. When data is valid it get persisted and redirect to list is issued (POST/Redirect/GET). When data is invalid thymeleaf's error fields are populated and I list error messages below appropriate fields.
The only thing I want to change, but I can't figure out how to, is to clear some data from model.
Things I tried so far: modifying #ModelAttribute parameter, setting attributes in Model, setting attributes in RedirectAttributes. Every time I get the very same data user provided without any changes in output form, for some reason I can't change a thing. I tried also redirecting to GET method but it seems it clears slate clean, which I don't want.
If someone is interested this is how form in thymeleaf looks:
<form id="content" action="#" th:action="#{/add}" th:object="${watchedDirectory}" method="post" class="was-validated">
<div class="form-group has-feedback has-error">
<label for="filesystemPath">Filesystem path:</label>
<input th:field="*{filesystemPath}" type="text" id="filesystemPath" name="filesystemPath" class="form-control" placeholder="~/" required />
<label th:if="${#fields.hasErrors('filesystemPath')}" th:errors="*{filesystemPath}"></label>
</div>
<button type="submit" class="btn btn-outline-success">Save</button>
</form>
required attribute on input field will shut up when provided with whitespace but there will be error message from Spring's validation. Clearing this field and returning it to user will make things more consistent than showing mixed signals such as:
Any help would be really appreciated.

You need to define a BeanPropertyBindingResult object that provides the fields having the errors. Then make a model with this results,
#PostMapping( "/add" )
public String addExecute( #Valid #ModelAttribute WatchedDirectory watchedDirectory, BindingResult result, RedirectAttributes redirect, Model model )
{
if( result.hasErrors() )
{
BeanPropertyBindingResult result2 = new BeanPropertyBindingResult( watchedDirectory, theBindingResult.getObjectName() );
for( ObjectError error : theBindingResult.getGlobalErrors() )
{
result2.addError( error );
}
for( FieldError error : theBindingResult.getFieldErrors() )
{
result2.addError( new FieldError( error.getObjectName(), error.getField(), null, error.isBindingFailure(), error.getCodes(), error.getArguments(), error.getDefaultMessage() ) );
}
model.addAllAttributes( result2.getModel() );
return "mng-dir-add";
}
watchedDirectory = fs.persistDirectory( watchedDirectory );
redirect.addFlashAttribute( "added", watchedDirectory );
return "redirect:/list";
}

According to Validation by Using Spring’s Validator Interface from the Spring Framework Documentation:
The Validator interface works by using an Errors object so that,
while validating, validators can report validation failures to the
Errors object.
validate(Object, org.springframework.validation.Errors): Validates
the given object and, in case of validation errors, registers those
with the given Errors object.
And the Errors interface doesn't provide any API for deregistering binding errors.
So, it seems there is no Spring provided way to achieve what you want.

Related

Spring modelattribute not recognized anymore after ajax update

i´ve encountered the following issue several times:
I use a Controller to bind a dto to a html form (via thymeleaf). Please note the model named "invoiceDto"
#RequestMapping(value = {"/create"}, method = RequestMethod.GET)
public String create(Locale locale, Model model) throws Exception {
final String login = this.getAuthentication().getCurrentApplicationUser();
if (login == null || login.isEmpty())
throw new Exception(this.getMessageSource().getMessage("label.findError", null, locale));
final Future<Setting> setting = settingService.findFirst();
final Future<ApplicationUserContactProjection> applicationUserContactProjection = applicationUserService.findByLogin(login);
while (!setting.isDone() && !applicationUserContactProjection.isDone()) {
Thread.sleep(100);
}
if (setting.get() == null || applicationUserContactProjection.get() == null)
throw new Exception(this.getMessageSource().getMessage("label.error.findError",
null, locale));
model.addAttribute("invoiceDto", new InvoiceDto(setting.get(), applicationUserContactProjection.get()));
model.addAttribute("message", this.getMessageSource().getMessage("label.navigation.invoiceCreation", null, locale));
return "invoice/create";
}
I have a html form (thymeleaf generated) where i use the above Java pojo dto with the given modelattribute name to fill my Input fields. This is an excerpt of it. The important part is the div with the id of "invoiceLineItems" where thymeleaf replaces its child div with a lineItems Fragment:
<form action="#" th:action="#{/invoice/newinvoice}" th:object="${invoiceDto}" role="form" method="post"
class="form-signin" id="editInvoiceForm"
accept-charset="utf-8">
<div id="invoiceLineItems"><div th:replace="invoice/items :: lineItems"></div></form>
The fragement contains the following stuff - an excerpt of it:
<td>
<input type="text"
th:field="*{items[__${index.index}__].lineItemTotalPrice}"
readonly
class="form-control" disabled
id="lineItemTotalPrice"/>
</td>
Excerpt of the given pojo:
public class InvoiceDto implements Serializable {
private Invoice invoice;
private List<LineItem> items;
I access the list like this:
th:field="*{items[__${index.index}__].lineItemTotalPrice}"
The Problem:
I can add items dynamically via Ajax. I serialize the whole form (for convenience reasons) and call a Controller Method:
#RequestMapping(value = {"/newlineitem"}, method = RequestMethod.POST)
public String newLineItem(#ModelAttribute("invoiceDto") InvoiceDto invoiceDto,
Model model)
throws Exception {
invoiceDto.addItem(new LineItem());
final Future<InvoiceDto> calculatedInvoiceDto = invoiceService.calculateInvoice(invoiceDto);
while (!calculatedInvoiceDto.isDone()) {
Thread.sleep(100);
}
model.addAttribute("invoiceDto", calculatedInvoiceDto.get());
return "invoice/dynamicitems :: lineItems";
}
As you can see, i let thymeleaf render a Special view, because after the Ajax success spring cannot set the modelattributes proper.
In short: After the Ajax Returns the partial view, the following will throw an exception:
th:field="*{items[__${index.index}__].lineItemTotalPrice}"
whereas this works - note the prefixed invoiceDto:
th:field="*{invoiceDto.items[__${index.index}__].lineItemTotalPrice}"
Question:
What´s wrong here?
Why do i have to prefix the name of the modelattribute after the partial Ajax update, whereas in the first run i don´t have to?
Thank you for your help!
EDIT:
For my share it looks like the way that spring "forgets" the originally named form modelattribute "invoiceDto" if the page is partially updated by an ajax call (to another spring controller, modifying invoiceDto) through a partial thymeleaf html.
So after the controller returns the partial thymeleaf view i have to access its fields with prefixed "invoiceDto", as if there would be no invoiceDto attribute.
Thanks again for your help!
UPDATE
As there is no progress on this i have raised a thymeleaf issue:
https://github.com/thymeleaf/thymeleaf/issues/795
Nevertheless i think this is a spring issue, because i have the same results with JSP.
Repository to comprehend this issue
https://mygit.th-deg.de/tlang/thymefail
If I understand your problem correctly, you're trying to use the *{} notation when there is no active object. When the ajax method returns just the "lineItems" fragment, Thymeleaf has no way of knowing that it belongs to a form with a th:object on it.
I guess the best solution is to return the whole form then, in js, extract the lineItems.
Or maybe just get rid of th:object altogether (convenient only when you want to show validation errors imho).

Spring MVC How to log all the errors from BindingResult

I have a controller which makes post method. The controller's method validates a entity. In case some errors occurred it redirects to error page otherwise it saves the entity. My code looks like that:
public String createEntity(Entity entity, BindingResult result) {
// Validate here
if(result.hasErrors) {
return "errorPage";
}
service.create(entity);
return "some view";
}
So now if there are errors I want to log them all. I've read this article
How to get error text in controller from BindingResult
but I don't want to type check.
Is there a clever way to do that?
Thank you.
it is very simple just add error list to your model
public String createEntity(Entity entity, BindingResult result,Model model) {
// Validate here
if(result.hasErrors) {
model.addAttribute("errors",result.getAllErrors());
return "errorPage";
}else {
service.create(entity);
return "some view";
}
}
later in your jsp :
<c:if test="${not empty errors}">
//foreach element show error (just an exampl)
</c:if>

How to get and post parameter from url

I got a page link like www.example.com/resetPassword?pass=33 and I can get this value from controller method with using #RequestParam method.
Problem is I also need this parameter(pass) value when I post this page(form).When I post below form url changes to www.example.com/resetPassword but I want also paramter as www.example.com/resetPassword?pass=33.Is there an easy way to achieve this?
Here is my form;
<form:form action="resetPassword.htm" role="form" method="POST">
....
</form:form>
My controller method;
#RequestMapping(value = "/resetPassword.htm*", method = {RequestMethod.GET, RequestMethod.POST})
public ModelAndView resetPassword(HttpServletRequest request, HttpServletResponse response, #RequestParam("pass") String pass){
if(request.getMethod().equals("GET")){
//
}
else if(request.getMethod().equals("POST")){
//
}
}
This is in general a bad idea. You are breaking restful convention.
The different request types normally have different behaviour, don;t try and combine them.

Neither BindingResult nor plain target object for bean in spring 3

Hi I am facing an issue and looked all over internet but still not able to find out the root cause. I am posting my code snippet please help me out as I am new to spring 3. I am using modelAttribute in form and what I want that in controller all the values from request should be backed in the object so that I can perform validation and other business logic.
I know there is mistake only in my controller.
1) index.jsp
<form:form action="login" method="POST" modelAttribute="login">
<table>
<tr><td>User Id:</td><td><form:input path="userId"/></td></tr>
<tr><td>Password:</td><td><form:password path="userPassword"/></td></tr>
<tr><td></td><td><input type="submit" value="Login"/></td></tr>
</table>
</form:form>
2) Controller
#RequestMapping(value="/login/", method=RequestMethod.POST)
public String login(#ModelAttribute("login") #Valid Login login,BindingResult result)
{
System.out.println("We have entered into controller class");
if(result.hasErrors())
{
System.out.println("Errors:"+result.getFieldError("userReject"));
return "redirect:/login";
}
else
{
return "home";}
}
}
3) JBoss Log
04:35:29,067 ERROR [org.springframework.web.servlet.tags.form.InputTag] (http--0.0.0.0-8090-1) Neither BindingResult nor plain target object for bean name 'login' available as request attribute: java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'login' available as request attribute
at org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:141) [spring-webmvc-3.0.5.Release.jar:3.0.5.RELEASE]
The problem is not in the method you posted, which handles the login form submission. It's in the method used to display the form. The form needs to populate its fields from a bean named "login", and you didn't place any bean named "login" in the model, i.e. in a request attribute.
Side note: a login form should never use GET. It should use POST. You really don't want the password to appear in the browser address bar. And you don't want it to appear in the browser history, the server and proxy logs, etc.

Route values disappeare in View .Net MVC3

I have simple controller:
public class TestController : Controller
{
public ActionResult Test(string r)
{
return View();
}
}
I have simple View Test.cshtml:
<h2>#ViewContext.RouteData.Values["r"]</h2>
#using (Html.BeginForm("Test", "Test"))
{
<input type="text" name="r" />
<button>Submit</button>
}
I have route rule in Global.asax:
routes.MapRoute(
null,
"Test/{r}",
new { action = "Test", controller = "Test",
r = UrlParameter.Optional }
);
I want to make such thing: user types route value in input, press submit and controller redirects him to page Test/value. But controller show just page with name Test everytime. ViewContext.RouteData.Values["r"] is empty too. I check in debug, Test action recieves user value of r correctly.
How can I realize my idea?
Thanks.
I'm super late to the party, but just wanted to post a solution for reference. Let's assume that this form has more than just a strong as it's input. Assuming there are other inputs, we can wrap up the inputs of the form into a class in our model, called TestModel whose properties maps to the id's of the form's inputs.
In our post, we redirect to the get, passing in the route values we need in the URL. Any other data can then be shuttled to the get using a TempData.
public class TestController : Controller
{
[HttpGet]
public ActionResult Test(string r)
{
TestModel model = TempData["TestModel"] as TestModel;
return View(model);
}
[HttpPost]
public ActionResult Test(string r,TestModel model) //some strongly typed class to contain form inputs
{
TempData["TestModel"] = model; //pass any other form inputs to the other action
return RedirectToAction("Test", new{r = r}); //preserve route value
}
}
You cannot do this without javascript. There are two types of methods that exist when submitting a <form>: GET and POST. When you use POST (which is the default), the form is POSTed to the url but all data entered in input fields is part of the POST body, so it is not part of the url. When you use GET, the input fields data is part of the query string but of the form /Test?r=somevalue.
I wouldn't recommend you trying to send user input as part of the path but if you decide to go that route you could subscribe to the submit event of the form and rewrite the url:
$('form').submit(function() {
var data = $('input[name="r"]', this).val();
window.location.href = this.action + '/' + encodeURIComponent(data);
return false;
});
As far as you are saying to post the form to Html.BeginForm("Test", "Test") you will be always posted back to the same page.
A solution could be to use an explicit Redirect to the action using 'RedirectToAction' (in view) or you can use javascript to change the form's action:
<input type="text" name="r" onchange="this.parent.action = '\/Test\/'+this.value"/>

Resources