I have an enum that I am validating like this:
#EnumValueValidator(enumClass = MyEnum.class)
String value
It's a string field, but it is validated against a list of enums. However, once the validation is completed, and I know the field is valid, I want the getter to return the enum.
So I added a getter like this
public MyEnum getValue()
return MyEnum.valueOf(value)
The problem is if the value is not one of the valid enum types, the validation doesn't issue the proper error message, because it gets an exception calling the getter on the invalid type. I don't want the validation to use the getter. It should use the field directly. The getter() should only be used by explicit calls in the business logic. Is there a way to do this?
Related
I am passing a request object name Person to controller. Lets say the object has 2 two fields. The following business rule apply:
If field age has a value < 18, the field sin should be left blank;
If not, it will produce exception with message the sin should be blank with age < 18 or another way is to set the field sin to empty string("").
What is the best way for me to validate those inputs when they depend on each other. My way to deal with them is to validate them inside the controller method. So it should look something like that
#GetMapping("/..."
public ResponseEntity<PersonResponse> getPersonResult(GetPersonRequest request)
{
if (request.getAge() < 18)
{
if (request.getSin.length > 0)
request.setSin("")
}
PersonResponse response = callThirdPartyAPIToRetrieveInformationAboutThatPerson(request)
return response ;
}
Is there any more elegant way to code ? Is it ok for the controller method to contain any validation logic like that ? am i violating the Single Responsibility in SOLID design ?
Yes, of course! And this is a good approach: single responsibility of classes - a controller is responsible for handling data, validator - for validation of data; open-closed principle - validated data is unchangeable by controller's method; Liskov principle correlates with the base OOP principles - a validator is separated entity and can be changed to another one without any additional manipulations; Interface Segregation is clear without any description (fully separated classes); Depency Inversion is also understandable - using annotation interface, controller does not know anything about its implementation. So, it's a really good approach from ideology and language syntax.
Implementation.
Create class-level #interface. All fields are accessible.
Create ConstraintValidator class with validation logic.
Set this annotation for #RequestBody in the controller method.
Add validation functionality for controller: #Validated for controller class and #Valid for #RequestBody entity in controller method.
If you need to handle validation exceptions, just throw a new exception and handle it in #ControllerAdvise class, no handling code in validation or controller classes.
Example of creation class-level validator in the official resource.
I have the following in one of my view models:
public char? MyChar { get; set }
I noticed there appears to be no client-side validation on the textbox for MyChar, and it's possible for a user to submit a string that is too long for this char to my controller (it simply doesn't bind and remains null).
So, I added the [MaxLength] annotation. Client-side validation now works as expected, but when I submit a valid value to the server, I get the following exception in my event logs:
Exception information:
Exception type: InvalidCastException
Exception message: Unable to cast object of type 'System.Char' to type 'System.Array'.
at System.ComponentModel.DataAnnotations.MaxLengthAttribute.IsValid(Object value)
How can I validate the max length of a char correctly both client-side and server-side?
What I've Tried
For now, I've created my own MaxLength attribute for it to work with char types, but it feels like a heavy approach.
My project uses validation block, and i am passing some complex data type from client side to server side model.
But sever side model accepts the null value as well as string values for the int data type property at server side, it sets the default value of constructor 1, how should i fix this issue?
public int Sequence { get; set; }
and I am passing "Sequence": "", from client side but this accepts it without error and sets the default value to 1 that is of constructor.
Or what annotation i should apply on it, i never used validation block.
One way to enforce constraints is to add them to the Route template in your controller. Example...
[Route("api/foo/{sequence:int}")]
public string Get(int sequence)
{
return "value";
}
I have a viewmodel with a property of type int?.
If the send value is an integer or is empty it works fine. When the value is a string an exception is thrown 'Could not convert string to integer'
Would it be possible to change the behavior of the binding that instead of throwing a error it would bind the null value ?
I've created a modelbinder class for typeof(int?), but the ModeBind function doesn't get called. I guess the validation happening before the model binding.
Any ideas ?
When you say you have a view model are you referring to the parameter that is being passed to your action method? E.g.
public Foo Bar(MyViewModel vm)
If so then your modelbinder should be of type MyViewModel. You can additionally use a ValueProvider to extract an int from a random string to override the default value provider functionality.
I have a struct that implements IValidatableObject, which I use for properties on a view model. The validation works fine with non-nullable properties, but if add a property of type Nullable<MyStruct> the IValidatableObject.Validate method is never called for that property, even if it has a value.
Is there any way that I trigger the validation on my nullable properties too, or is IValidatableObject not intended to be used with value types?
Thanks
Additional information:
I wanted to be able to specify units when entering data in my form, e.g. 10m, 100km, 1cm etc. I also wanted to validate that the entered data was within range and of a correct format. My struct converts the string with potentially different units (100cm/100m/1km) into a decimal property which always has the same unit (1/100/1000) and vice-versa.
I used IValidatableObject for validation because the validation will always be the same for any instance of MyStruct - I didn't want to add an attribute each time it is used.
My model would look something like this:
public class MyModel
{
public MyStruct Ab {get; set;}
public MyStruct? Cd {get; set;}
}
My view is a strongly typed view using the model, which renders my struct as a text box using a display template. When the form is posted, I have a custom model binder to convert the entered string back to my struct.
My controller action looks like:
public ActionResult MyAction(MyModel model)
{
//do stuff
}
The model is bound ok for properties of type MyStruct and Nullable<MyStruct>.
It seems that this is not possible.
I dug into the MVC source. In System.Web.Mvc.DataAnnotationsModelValidatorProvider there is the following code:
// Produce a validator if the type supports IValidatableObject
if (typeof(IValidatableObject).IsAssignableFrom(metadata.ModelType)) {
//stuff
}
According to this rejected bug report, Nullable<T> cannot be assigned to an interface that T implements:
"In short, a "DateTime?" value can be unboxed to a DateTime or an IFormattable value. But it cannot be assigned to a DateTime or IFormattable location."
Thus no validators are returned.
Looks like I will have to do this another way.