Using the Spring 2.5 MVC annotation framework, what is the proper way to validate request parameters, including the type of the parameters?
I've searched these these forums (e.g. this) and Spring tutorials (e.g. this), and I can't find a good answer. I've read replies stating that validation should be separate from binding, but for some cases, validation errors can occur DURING binding, e.g. attempting to bind a non-integral string to an integer results in a NumberFormatException, but this causes the whole request to fail instead of logging it into some binding result.
I tried this at first:Code:
@Controller
public class SomeController { ...
@RequestMapping public String service(@RequestParam(value=quot;limitquot;, required=false) Integer limit,@RequestParam(value=quot;statesquot;, required=false) Setlt;TaskStategt; states,ModelMap model) { if (limit == null) {limit = DEFAULT_LIMIT; } Listlt;TaskSetInstancegt; taskSets = taskDAO.findTaskSets(states, limit); ... }
}
But if the user enters quot;limit=aquot; or quot;states=nonexistentstatequot; in the ucl, the request immediately fails with no way for me catch the TypeMismatchException.
Needless to say, this is incredibly fragile, and I can't believe that a supposedly robust framework doesn't have a way to handle this.
Furthermore, even if I could catch the exception, where should I be logging the error? According to the docs, I need to put it in a BindingResult, and I can only do that if I have a @ModelAttribute. But a request param can't be both @RequestParam and @ModelAttribute. So would I need to do something like this?Code: @RequestMapping public String service(@RequestParam(value=quot;limitquot;, required=false) Integer limit,@RequestParam(value=quot;statesquot;, required=false) Setlt;TaskStategt; states,@ModelAttribute(quot;taskSetsquot;) ArrayListlt;TaskSetInstancegt; taskSetModels,BindingResult result) { if (limit == null) {result.reject(quot;using.default.limitquot;, quot;using default value for limit paramquot;);limit = DEFAULT_LIMIT; } ... }
So I'm wondering if I'm not getting how validation is supposed to work in Spring MVC... Thanks for any help.
First off all what you are trying to do isn't binding, binding and validation is al done on the form object (or ModelAttribute). In that case there is also a BindingResult in your case there isn't because it isn't binding nor validation.
You are simply retrieving values from the fromServletRequest and try to convert them to a certain type. This isn't binding at least not in the terms that Spring MVC uses. You can create your own BindingErrorProcessor which can handle this for you. Configure a ConfigurableWebBindingInitializer and register it on there.
Will see if I can create a small sample which has some exception handling working.
A pattern I have found works nicely for me is to always back my requests by specific classes.
For example, if I have a form submission with some data in it, I will create a class by the Value Object pattern which represents exactly the data expected from the particular submission. The object is passed to my controller method by a @ModelAttribute argument, and I am free to implement validation as I see fit, which is made significantly easier by the fact that I know what to expect from the submission.
The validation pattern I tend to prefer is self-validating domain objects, which both keeps validation algorithms encapsulated within the domain objects, and forces appropriate handling of validation problems.
Thanks for the replies. I'd really appreciate that sample.
The way I see it, checking the type of a parameter IS validation. For example, if checking that an SSN is well-formed is validation, then checking that an integer is well-formed should also be validation. From an abstract standpoint, both SSN and integers are types, with SSNs just being a more specific type of integer.
So to me, this weird distinction between what is validation and what isn't is just confusing.
From a Domain Driven perspective validation belongs in your domain model (or value objects). Which is basically what jamestastic is suggesting with his solution. Spring also uses this perspective.
Also in your case it is easy to say if something is an valid Int, but what if something is valid depending on the values of 3 other fields that isn't easy validation. Or what if it are only certain ranges, there is no (really easy or straight forward) way one could make that easier.
So I should have a model class for every request method that should handle validation? Like:Code:
@RequestMapping
public String getTaskSet(WebRequest request,
@ModelAttribute TaskSetRequestValidator taskSetRequestValidator, BindingResult result) { taskSetRequestValidator.validate(request, result); ...
}
But that just adds another layer of abstraction - in fact, you'd think I could just define some TaskSetRequest that gets created automatically for each mapped request, and performs all the validation and actions of the controller, without even needing to define a controller, e.g.Code:
// alternate framework example
@RequestMapping(quot;viewTaskSetsquot;)
public class TaskSetsRequest { @RequestParam(quot;limitquot;) Integer limit; @RequestParam(quot;statesquot;) HashSetlt;TaskStategt; states; @ModelAttribute(quot;taskSetsquot;) HashSetlt;TaskSetInstancegt; taskSets; protected void validate(BindingResult result) { ... } protected String evaluate() { ... }
}
// in view (using FreeMarker):
Errors during request param validation:
lt;@spring.bind quot;taskSetsRequestquot;/gt;
lt;@spring.showErrors/gt;
Which is pretty much the same thing as before but in a different style, where each request is mapped to a class rather than a method on a controller.
Or maybe I'm just not following what you are talking about.
Which basically goes against the OO principle of single responsibility. A class should be responsible for 1 thing and do that thing well. Your class is a Model/Controller/Validator/Domain object basically 4 in one.
In Spring MVC you haveView -gt; The jsp which uses the Model to render itself
Controller -gt; Which changes the model and selects the view to render
Model -gt; The data needed to render the model and your Form Object is part of the model
The only thing you need to do it to create a form object, object used to bind to. That object can be your normal domain/persistence object.
I suggest you read the MVC chapter in the spring reference guide that explains everything in more detail.
I have read that chapter but it doesn't really address my issue.
My main problem is that some validation cannot occur in a single step, like the type conversion resulting in the TypeMismatchException, unless I treat all request parameters as strings initially. I'm currently resorting to code like this:Code:
@RequestMapping(params=quot;limitquot;)
public String getTaskSets(@RequestParam(quot;limitquot;) String paramLimit, @RequestParam(value=quot;statesquot;, required=false) String[] paramStates, @ModelAttribute(quot;limitquot;) MutableInt limit, BindingResult resultLimit, @ModelAttribute(quot;statesquot;) HashSetlt;TaskStategt; states, BindingResult resultStates, @ModelAttribute(quot;taskSetsquot;) HashSetlt;TaskSetInstancegt; taskSets, BindingResult resultTaskSets, Maplt;String, Objectgt; model) { // parse paramLimit and put it into limit, adding errors to resultLimit // parse paramStates and put it into states, adding errors to resultStates // get task sets from DAO and put them into taskSets, adding any errors to resultTaskSets // return view/redirect
}
This ends up being verbose - and IMO ugly - code. I have since factored out the parsing, and adding to the BindingResult, but I can't help but feel that this should be a common problem and thus there should already be a common solution to it.
That is what I was trying to explain all along, you need an object which represents your HTML form that way properties are bound/converted/validated in one go. Code:
public class MyForm {
private MutableInt limit; private Setlt;TaskStategt; states; private Setlt;TastSetInstancegt; taskSets;
//appropriate getter/setters
}
Your controller
Code:
public String getTaskSets(@ModelAttribute(quot;formquot;) MyForm form, BindingResult result, Model model) { validator.validate(form, result); if (result.hasErrors) { //return to original page } // do business logic here
}
To convert to and from objects you need some property editors which you can register and take care of conversion. This way you get all what you want....
Ok I see what you mean by a form object. That was what I was going for in my previous post (the one with the TaskSetsRequest class), but I was also adding in controller and validation into a single class for convenience.
Now, what I still don't get is where/how the request parameters are passed to the form setters. Is this done automatically? I did look at the Spring source code (DefaultAnnotationHandler, IIRC), and I may have missed it. I'll take another look once I get back to the office...
Another issue is what I should do when parsing the request parameter in a PropertyEditor. If I throw an exception on failure, I'm back to square one, so it would be nice if I had access to a BindingResult object within the PropertyEditor to report errors during type conversion.
Thanks for the help. |