Confused as to how to validate spring mvc form, what are my options?

Go To StackoverFlow.com

5

Latest spring mvc, using freemarker.

Hoping someone could tell me what my options are in terms of validating a form with spring mvc, and what the recommend way would be to do this.

I have a form that doesn't map directly to a model, it has input fields that when posted, will be used to initialize 2 model objects which I will then need to validate, and if they pass I will save them.

If they fail, I want to return back to the form, pre-fill the values with what the user entered and display the error messages.

I have read here and there about 2 methods, once of which I have done and understand how it works:

@RequestMapping(...., method = RequestMethod.POST)
public ModelAndView myMethod(@Valid MyModel, BindingResult bindingResult) {
  ModelAndView mav = new ModelAndView("some/view");
  mav.addObject("mymodel", myModel);

  if(bindingResult.hasErrors()) {
     return mav;
  }

}

Now this worked if my form mapped directly to the form, but in my situation I have:

  1. form fields that don't map to any specific model, they have a few properties from 2 models.

  2. before validation occurrs, I need to create the 2 models manually, set the values from the form, and manually set some properties also:

  3. Call validate on both models (model1, model2), and append these error messages to the errors collection which I need to pass back to the same view page if things don't work.

  4. when the form posts, I have to do some database calls, and based on those results may need to add additional messages to the errors collection.

Can someone tell me how to do this sort of validation?

Pseudo code below:

   Model1 model1 = new Model1();
   Model2 model2 = new Model2();

   // manually or somehow automatically set the posted form values to model1 and model2.

   // set some fields manually, not from posted form
   model1.setProperty10(GlobalSettings.getDefaultProperty10());
   model2.setProperty11(GlobalSettings.getDefaultProperty11());

   // db calls, if they fail, add to errors collection

   if(bindingResult.hasErrors()) {
     return mav;
   }

   // validation passed, save
   Model1Service.save(model1);
   Model2Service.save(model2);

   redirect to another view

Update

I have using the JSR 303 annotations on my models right now, and it would great if I can use those still.

Update II

Please read the bounty description below for a summary of what I am looking for.

2012-04-04 16:53
by Blankman


8

Based on a similar experience, I'd propose the following and on the side I have a comment on the last step on the approach that you want to take. I use your numbered list of steps.

Step 1: Form Bean

There are two ways for this. The simple approach is to define a form bean (that I presume you've already done):

class MyModel {
  private Model1 model1;
  private Model2 model2;
  // standard Java bean stuff
}

A more precise way is to actually define MyModel such that I only borrows the fields that requires from Model1 and Model2 but I'm not sure if this fits your way.

Step 2: Data Binding

Spring does this for you if you have such form structure in your view:

<form:form modelAttribute="myModel">
  <form:input path="model1.somePropertyToBeSet" />
</form:form>

Step 3: Validation

Using Spring custom validations, you can define custom constraints:

@interface Model1Constraint {}
@interface Model2Constraint {}

class MyModel1 {

  @Model1Constraint
  private Model1 model1;

  @Model2Constraint
  private Model2 model2;

  // ...

}

Then register your custom validators for the custom constraints:

class Model1ConstraintValidator implements ConstraintValidator<Model1Constraint, Model1> {
// implementation of isValid and initalize
}

And the same for Model2Constraint. Using the custom validators, you can check what logic you need to ensure before MyModel is passed into request processing method. I also presume that you've used <mvc:annotation-driven /> to have Spring register the validators; otherwise, you should configure them.

Step 4: Custom model processing before request processing

Your original idea is to use some data binder for this job. In your description, you also mention that this data processing does not depend on the data coming from the form data.

Regarding design and modularity, I do not believe a data binder is a good place for such a purpose. Second to that, since there is no data dependency to the form, the major reason for you is to allow data binding error processing.

So, my suggestion is that let's say that now you're in public ModelAndView myMethod(@Valid MyModel model, BindingResult bindingResult). Presumably, you have access to other service beans here. So, you can have a method in some service bean that can refine or prepare (just names) the model that you've populated in this point. Based on exceptions or any other mechanism that suits you, you can use bindingResult to return the errors again.

As another suggestion, you may also take advantage of Spring interceptors if you'd like a more DI/IoC way to do it. But in this way, you should extract MyModel from ModelAndView in interception and proceed.

I hope this helps.

2012-04-08 17:27
by nobeh


1

awesome explaination here, Please check Integrating Spring MVC 3.0 with JSR 303 (aka javax.validation.*)

Check this below example too JSR 303 Bean Validation Using Spring 3

2012-04-13 12:05
by developer


0

Hibernate Validator 4.2 supports method level validation. You can refactor your code a bit to pass the two models in a method, and validate them.

http://java.dzone.com/articles/method-validation-spring-31

you can have something like this

public void persistUser(@NotNull @Valid Model1 model1, @NotNull @Valid Model2 model2) {

      //continue ...
}   
2012-04-09 13:27
by Sinisha Mihajlovski


0

This is an unusual problem, as it usually makes the most sense to validate the user input, as that is the data we can't control. With that being said, and since I'm sure you knew that already...

One option would be to use the JSR 303 validation api directly to validate your model objects after they've been populated from user input, database, etc..

Here's an example:

@RequestMapping(value=...)
public String myMethod(MyForm form, Model m) {

    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    Validator validator = factory.getValidator();

    Model1 model1 = createModel1FromUserInput(form);
    Model2 model2 = createModel2FromUserInput(form);

    Set<ConstraintViolation<?>> modelViolations = validator.validate(model1);
    modelViolations.add(validator.validate(model2));
    if(modelViolations.size() != 0) {
        m.addAttribute("violations", modelViolations);
        m.addAttribute("form", myForm); // add the form back to the model so it is populated
        return "formPage";
    }
    return "successPage";
}

You may prefer to bind to a Spring BindingResult or a collection of errors. I haven't tested the following code, and am not very comfortable working directly with BindingResult, so it will require tweaking:

BindingResult result = ... // not sure about the constructor
for(ConstraintViolation<?> violation : modelViolations) {
        result.addError(new ObjectError(violation.getPropertyPath().toString(),violation.getMessage()));
}

If you are just trying to spit out the errors on the JSP, then using a Set<ConstraintViolation> may work well enough for your needs:

<c:forEach items="${violations}" var="violation">
    ${violation.propertyPath}: ${violation.message} <br/>
</c:forEach>

If you are trying to use Spring's <form:errors> tag, you'll need to use the binding result.

HTH! Let me know if you have any more questions. Or if I completely missed the mark on this answer, I'll try to clarify.

2012-04-09 15:36
by stephen.hanson
Ads