How do I use IValidatableObject?
First off, thanks to @paper1337 for pointing me to the right resources...I'm not registered so I can't vote him up, please do so if anybody else reads this.
Here's how to accomplish what I was trying to do.
Validatable class:
public class ValidateMe : IValidatableObject
{
[Required]
public bool Enable { get; set; }
[Range(1, 5)]
public int Prop1 { get; set; }
[Range(1, 5)]
public int Prop2 { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
if (this.Enable)
{
Validator.TryValidateProperty(this.Prop1,
new ValidationContext(this, null, null) { MemberName = "Prop1" },
results);
Validator.TryValidateProperty(this.Prop2,
new ValidationContext(this, null, null) { MemberName = "Prop2" },
results);
// some other random test
if (this.Prop1 > this.Prop2)
{
results.Add(new ValidationResult("Prop1 must be larger than Prop2"));
}
}
return results;
}
}
Using Validator.TryValidateProperty()
will add to the results collection if there are failed validations. If there is not a failed validation then nothing will be add to the result collection which is an indication of success.
Doing the validation:
public void DoValidation()
{
var toValidate = new ValidateMe()
{
Enable = true,
Prop1 = 1,
Prop2 = 2
};
bool validateAllProperties = false;
var results = new List<ValidationResult>();
bool isValid = Validator.TryValidateObject(
toValidate,
new ValidationContext(toValidate, null, null),
results,
validateAllProperties);
}
It is important to set validateAllProperties
to false for this method to work. When validateAllProperties
is false only properties with a [Required]
attribute are checked. This allows the IValidatableObject.Validate()
method handle the conditional validations.
Conditional Validation in MVC using IValidatableObject
In your model/class that implemented the IValidatableObject, try doing something like this:
...
public List<ValidationResult> ValidationResults { get; } = new List<ValidationResult>();
public bool TryValidate(out List<ValidationResult> vResults)
{
vResults = ValidationResults;
var context = new ValidationContext(this);
Validate(context);
var fieldValidations = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(context.ObjectInstance, context, fieldValidations,
validateAllProperties: true);
//Add any attribute validation errors to ValidationResults
if (!isValid)
{
foreach (var validationResult in fieldValidations)
{
ValidationResults.Add(validationResult);
}
}
//Add your custom validations
if (!IsDraft() && Message.IsStringBlank())
{
ValidationResults.Add(new ValidationResult("Message cannot empty");
}
isValid = !ValidationResults.Any();
return isValid;
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (_isClassValidate) return new List<ValidationResult>();
_isClassValidate = true;
return ValidationResults;
}
ASP.NET Core - Create custom model validation
There are two ways to do custom model validation in ASP.NET Core:
- A custom attribute subclassed from
ValidationAttribute
. This is useful when you want to apply custom business logic to a particular model property with an attribute. - Implementing
IValidatableObject
for class-level validation. Use this instead when you need to do validation on an entire model at once.
The documentation has examples of both. In your case, IValidatableObject
would probably be the best approach.
Is it possible to use IValidatableObject with Winforms?
Finally I found the solution (and my error) thanks to the MSDN article :
http://msdn.microsoft.com/en-us/data/gg193959.aspx
When I read it the first time, I missed a part while implementing the interface.
I forgot to define the memberNames
parameter of the ValidationResult
class.
So I just changed :
ValidationResult result = new ValidationResult("Actual Date connot be > to ValidityDate");
to
ValidationResult result = new ValidationResult("Actual Date connot be > to ValidityDate",
new[] { nameof(ActualDate), nameof(ValidityDate) });
And it worked as expected.
I am still wondering what value to pass in ValidationContext
parameter if the Validate
method of the interface is called manually, but it is another question.
So to answer my own question : Yes, it is possible to use IValidatableObject
with Winforms.
How to show error message using IValidatableObject in WPF?
Your problem is because IValidateObject ONLY returns those validation results.
WPF doesn't care about them.
You need to implement inotifydataerrorinfo or another interface that WPF understands and use those results in that.
You could adapt the code I use in this:
https://gallery.technet.microsoft.com/scriptcenter/WPF-Entity-Framework-MVVM-78cdc204
Take a look at the code in BaseEntity.
IValidatableObject Validate method firing when DataAnnotations fails
Considerations after comments' exchange:
The consensual and expected behavior among developers is that IValidatableObject
's method Validate()
is only called if no validation attributes are triggered. In short, the expected algorithm is this (taken from the previous link):
- Validate property-level attributes
- If any validators are invalid, abort validation returning the failure(s)
- Validate the object-level attributes
- If any validators are invalid, abort validation returning the failure(s)
- If on the desktop framework and the object implements IValidatableObject, then call its Validate method and return any failure(s)
However, using question's code, Validate
is called even after [Required]
triggers. This seems an obvious MVC bug. Which is reported here.
Three possible workarounds:
There's a workaround here although with some stated problems with it's usage, apart from breaking the MVC expected behavior. With a few changes to avoid showing more than one error for the same field here is the code:
viewModel
.Validate(new ValidationContext(viewModel, null, null))
.ToList()
.ForEach(e => e.MemberNames.ToList().ForEach(m =>
{
if (ModelState[m].Errors.Count == 0)
ModelState.AddModelError(m, e.ErrorMessage);
}));Forget
IValidatableObject
and use only attributes. It's clean, direct, better to handle localization and best of all its reusable among all models. Just implement ValidationAttribute for each validation you want to do. You can validate the all model or particular properties, that's up to you. Apart from the attributes available by default (DataType, Regex, Required and all that stuff) there are several libraries with the most used validations. One which implements the "missing ones" is FluentValidation.- Implement only
IValidatableObject
interface throwing away data annotations. This seems a reasonable option if it's a very particular model and it doesn't requires much validation. On most cases the developer will be doing all that regular and common validation (i.e. Required, etc.) which leads to code duplication on validations already implemented by default if attributes were used. There's also no re-usability.
Answer before comments:
First of all I've created a new project, from scratch with only the code you provided. It NEVER triggered both data annotations and Validate method at the same time.
Anyway, know this,
By design, MVC3 adds a [Required]
attribute to non-nullable value types, like int
, DateTime
or, yes, decimal
. So, even if you remove required attribute from that decimal
it works just like it is one there.
This is debatable for its wrongness (or not) but its the way it's designed.
In you example:
- 'DataAnnotation' triggers if [Required] is present and no value is given. Totally understandable from my point of view
- 'DataAnnotation' triggers if no [Required] is present but value is non-nullable. Debatable but I tend to agree with it because if the property is non-nullable, a value must be inputted, otherwise don't show it to the user or just use a nullable
decimal
.
This behavior, as it seems, may be turned off with this within your Application_Start method:
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
I guess the property's name is self-explanatory.
Anyway, I don't understand why do you want to the user to input something not required and don't make that property nullable. If it's null then it is your job to check for it, if you don't wan't it to be null, before validation, within the controller.
public ActionResult Index(HomeViewModel viewModel)
{
// Complete values that the user may have
// not filled (all not-required / nullables)
if (viewModel.Decimal == null)
{
viewModel.Decimal = 0m;
}
// Now I can validate the model
if (ModelState.IsValid)
{
HomeModel.ProcessHome(viewModel);
return RedirectToAction("Ok");
}
}
What do you think it's wrong on this approach or shouldn't be this way?
Related Topics
C# Difference Between == and Equals()
How to Make a Textbox That Only Accepts Numbers
An Async/Await Example That Causes a Deadlock
Set Object Property Using Reflection
Use of Finalize/Dispose Method in C#
How to Save Application Settings in a Windows Forms Application
Pass an Instantiated System.Type as a Type Parameter For a Generic Class
How to Generate a Random Integer in C#
How to Create a Dropdownlist from an Enum in ASP.NET MVC
Performance Differences Between Debug and Release Builds
Creating a Byte Array from a Stream
How To: Execute Command Line in C#, Get Std Out Results
Join/Where With Linq and Lambda
How to Parse a String With a Decimal Point to a Double
How to Find the Text Within a Div in the Source of a Web Page Using C#