IValidatableObject Validate method runs twice - c#

I have a model with an Entity Framework object on it. The EF object implements IValidatableObject and has a Validate() method on it.
For some reason the method runs twice, so I get two identical model errors on my page.
Any idea why this happens or how to stop it?
I tried adding an _isValidated private member variable but it appears to be resetting to false every time it runs so it must be creating and validating two instances of the model.
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrWhiteSpace(CatName))
{
yield return new ValidationResult("Bad kitty", new string[] { "CatName", "CatName" });
}
}
Edit: My model:
public class KittyModel
{
public Cat Cat { get; set; }
public int? SomeId { get; set; }
public string SomeString { get; set; }
}
Then Cat is just an EF object
[MetadataType(typeof(CatMetadata))]
public partial class Cat : IValidatableObject
{
public sealed class CatMetadata
{
[Required]
public int? CatTypeID { get; set; }
}
// Some other get; only properties here
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrWhiteSpace(CatName))
{
yield return new ValidationResult("Bad kitty", new string[] { "CatName", "CatName" });
}
}
}

I ran into the same problem today... and I believe this is the reason that Validation Method is called 2 time, from here:
If your model is a complex model inside of a complex model, validation
might be called twice for model-level validators (which
IValidatableObject is considered to be). That's because it's validated
once as a stand-alone object, and then again as the property of its
containing object.

Related

How to perform validation of a model of an inherited class when its base class also has validation?

Well, my problem is that I am creating an api using aspnetcore 2.1, to avoid code duplication I have created an abstract class with the properties that share the dtos (board, boardforcreation, boardforupdate, etc). I added to the abstract class personalized validation using ivalidatableobject, now, I want to add personalized validation to the classes that derive from the abstract class but it tells me that extending from ivalidatableobject interface is rebundant because it was already declared in the base class and also when I add the Validate method in the derived class it tells me that it is already declared and implemented, then, how can I add validation in an abstract class and in a derived class using ivalidatableobject? or is there another way to achieve this. Thank you in advance.
public class Board : BoardAbstractBase, IValidatableObject
{
public Guid BoardId { get; set; }
public DateTimeOffset StartDate { get; set; }
public DateTimeOffset EndDate { get; set; }
}
public abstract class BoardAbstractBase : AbstractBasicEntity, IValidatableObject
{
public DateTimeOffset EstimatedStartDate { get; set; }
public DateTimeOffset EstimatedEndDate { get; set; }
public decimal EstimatedBudget { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!(EstimatedStartDate < EstimatedEndDate))
yield return new ValidationResult(
"StartDateBeforeEndDate|The estimated start date should be smaller than the end date.",
new[] {"BoardAbstractBase"});
}
}
Add a virtual method to your base class.
If you want to perform some common validation logic in the base class as well as perform extra validation logic in each of the concrete implementations, then add an virtual validation method to your base class that is called within the base class validation function.
Add to your base class:
public abstract class BoardAbstractBase {
...
protected virtual bool RepoValidate() {
return true;
}
...
}
Then in each concrete implementation implement RepoValidate with whatever custom validation logic you need as protected override bool RepoValidate() {...}.
For Example
public class Board : BoardAbstractBase, IValidatableObject
{
...
protected override bool RepoValidate() {
return this.BoardId == "";
}
...
}
Then in BoardAbstractBase.Validate:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!(EstimatedStartDate < EstimatedEndDate))
yield return new ValidationResult(
"StartDateBeforeEndDate|The estimated start date should be smaller than the end date.",
new[] {"BoardAbstractBase"});
if (!this.RepoValidate()){ ... }
}
Now, you can always modify RepoValidate to return a validation result if it fails, or take any argument, but just for the sake of example, this one simply returns false. Also, because it is virtual and not abstract, you only need to override it when you have extra custom logic to perform.
Use virtual and use return yield.
IValidatableObject is a very clear interface with a very clear intent in mind - it is used in the Controllers like this
if (ModelState.IsValid) {....}
So if your Action looks something like this (nice and clean)
public Task<IActionResult> DoSomeAction(SomeClass model)
{
if (ModelState.IsValid) {
...
}
else return View(model);
}
then your code will be something like
public BaseClass : IValidatableObject
{
// and implements
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (...)
yield return new ValidationResult($"...error - ",
new string[] { "variable1 name" });
if (...)
yield return new ValidationResult($"...error2 - ",
new string[] { "variable1", "variable2" });
}
public class SomeClass : SomeBaseClass, IValidatableObject
{
// and implements
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var baseErrors = base.Validate(validationContext);
if (baseErrors != null && baseErrors.Any())
{
foreach (var item in baseErrors)
{
yield return item;
}
}
if (...some error condition...)
yield return new ValidationResult($"Validation error - condition 1",
new string[] { "variable1 });
}
my point being that this is an iterative process - each subclass must "inherit" all the validation checks of its parents, and add new validation checks on top of them.
Similar to the accepted answer, what you could is make the Base Class implementation of the Validate method virtual then in your child class, override the Validate method, return inherited result first then add custom validation logic for the child class. See simplified example below
public abstract class BoardBase: IValidatableObject
{
public int Id { get; set; }
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
if (Id != 1)
{
results.Add(new ValidationResult("Id must be 1"));
}
return results;
}
}
public class Board: BoardBase
{
public string Name { get; set; }
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = base.Validate(validationContext).ToList();
if (Name.Length > 4)
{
results.Add(new ValidationResult("Name must be shorter than 5"));
}
return results;
}
}

IValidatableObject not validating if child collection is Invalid

I have an View Model that is an IValidatableObject that contains a collection of CustomFields that are also IValidatableObject. Both the view model and the custom fields have custom logic to check if they are valid when posted.
The View Model looks like this:
public class ViewModel : IValidatableObject
{
public bool IsInspected { get; set; }
public DateTime? InspectedDate { get; set; }
public IList<CustomField> CustomFields { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (IsInspected == true && InspectedDate == null)
{
yield return new ValidationResult("Inspection Date is required if Inspected.", new[] { nameof(InspectedDate) });
}
}
}
public class CustomField : IValidatableObject
{
public bool IsRequired { get; set; }
public string Value { get; set; }
public string DisplayName { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (IsRequired && string.IsNullOrWhiteSpace(Value))
{
yield return new ValidationResult($"The {DisplayName} field is required.", new[] { nameof(Value) });
}
}
}
This correctly validates the CustomFields, but it doesn't validate the ViewModel itself unless the CustomFields have no errors. This means if the user posts invalid Custom Fields and invalid View Model fields, they aren't given notification of the View Model fields until they correct the Custom Fields, post again, and then get the View Model fields validated.
I tried removing IValidatableObject from CustomField and instead doing a foreach loop inside the ViewModel.Validate() method, but that didn't correctly assign ModelState Keys that highlight the input fields on the form. For example the method shown above creates a ModelState Key of CustomFields[0].Value while doing a loop inside ViewModel just creates a Key of Value.

Validation strategy

I'm trying to build a series of attribute classes to make it easier for our development team to validate objects. The objects are POCO classes like this.
public class User
{
public string Name { get; set; }
public string Company { get; set; }
}
I want to decorate this model with a custom attribute.
public class User
{
[MustHaveValue]
public string Name { get; set; }
public string Company { get; set; }
}
Then I would create my own class implementing ValidationAttribute, the base class in .NET Framework, which belongs to System.ComponentModel.DataAnnotations.
public class MustHaveValueAttribute : ValidationAttribute
{
.
.
public override IsValid(object value)
{
// validation logic.
}
}
And then I can validate the User model whenever I want by making the set of instances like ValidationContext, List<ValidationResult>.
But in an enterprise environment, problems just can't be solved by a specific class. My validation scenario requires more complex and more flexible ways. Imagine that one of the required validation scenarios would something like this.
public class User
{
public string Name { get; set; }
public string Company { get; set; }
// Check if an item exists in this list.
[MustHaveMoreThanOneItem]
public IList<Client> Clients { get; set; }
}
Then I would need to make another attribute class
public class MustHaveMoreThanOneItemAttribute : ValidationAttribute
{
.
.
public override IsValid(object value)
{
// Let's assume this value is List<Client> for now.
// I know the exact type, so I'm going to cast it to List<Client> without further considerations
List<Client> clients = value as List<Client>;
if(clients.Count > 0) {
return true;
} else {
return false;
}
}
}
But the problem is that there are a lot of other models that have a nested list items. Try to imagine the time when I want to reuse the MustHaveMoreThanOneItem in one of the other models like...
public class Department
{
public string Name { get; set; }
[MustHaveMoreThanOneItem]
public IList<Employee> { get; set; }
}
You already know that it's not going to work because it was strongly typed only for List<Client>. So I decided to use Generic there to solve this problem.
But to my disappointment, the _Attribute interface doesn't support Generic. There's no additional implementation like _Attribute<T> : Attribute and therefore, no ValidationAttribute<T> alas!! I just cannot use Generic here !!
public class Department
{
public string Name { get; set; }
// No way to use this syntax.
[MustHaveMoreThanOneItem<Employee>]
public IList<Employee> { get; set; }
}
So I made a conclusion that Attribute must have been designed for a fixed set of validations like email format, card format, null check, and etc IMAO.
But I still want to use an attribute and give a lot of flexibilities in it to prevent the duplicated, verbose validation codes like this.
if(model.Clients.Count > 0) ...
if(model.Name != null) ...
if(model.Clients.GroupBy(x => x.Country == Country.USA).Count >= 1) ...
if(model.Clients.Where(x => x.CompanyName == Company.Google).ToList().Count > 1 ) ...
.
.
.
I want to pose two questions here.
If Attirbute supports Generic, this problem will be solved?
Is there any way to implement Generic Attribute? in order to use
[MustHaveMoreThanOneItem<Employee>] annotation on a class member?
You can generically check any object that implements IEnumerable like this:
public class MustHaveMoreThanOneItemAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
// omitted null checking
var enumerable = value as IEnumerable;
var enumerator = enumerable.GetEnumerator();
if (!enumerator.MoveNext())
{
return false;
}
if (!enumerator.MoveNext())
{
return false;
}
return true;
}
}
C# by definition does not support generic type attributes, although this has been requested actively for a long time:
https://github.com/dotnet/roslyn/issues/953
https://github.com/dotnet/csharplang/issues/124
However, you can still inject a type into a validation attribute via constructor. You then can use reflection or whatever you need to define your custom validation criteria.
public class MustHaveMoreThanOneItemAttribute : ValidationAttribute
{
public Type EnumerableType { get; }
public MustHaveMoreThanOneItemAttribute(Type t)
=> this.EnumerableType = typeof(ICollection<>).MakeGenericType(t);
public override bool IsValid(object value)
{
var count = this.EnumerableType.GetProperty("Count").GetValue(value) as int?;
return (count ?? 0) > 1;
}
}
Now this allows you to use something similar to your goal:
public class Department
{
public string Name { get; set; }
[MustHaveMoreThanOneItem(typeof(Employee))]
public IList<Employee> { get; set; }
}

Why and how to fix ASP MVC model validation for child entities is called but not for parent entity?

I am using IValidatableObject validation for entities with e.g. following code:
public class OuterObj : IValidatableObject
{
public int ID { get; set; }
public string Name { get; set; }
public IEnumerable<InnerObj> InnerObjList { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (this.ID <= 0)
{
yield return new ValidationResult("", new[] { nameof(this.ID) });
}
}
}
public class InnerObj : IValidatableObject
{
public int ID { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (this.ID <= 0)
{
yield return new ValidationResult("", new[] { nameof(this.ID) });
}
}
}
In this case when I am validating the outerObj, when there are innerObj present it validates only the innerobj and not the outerobj. It doesn't reach the outerobj validate method in case of presence of innerobj.
I would like to validate both when innerobj present. Please help me with how its done. Why does it not validate the outerobj?
MVC 5.2.3
If the parent class has properties with validation attributes, and any of those properties have been evaluated as invalid, then the IValidatableObject.Validate implementation will not be invoked on the parent class.
This seems to be some sort of short-cutting that MVC is performing for model validation.
Your example does not show validation attribute(s) in the parent class - I'm assuming they were left out.
The workaround is remove validation attributes on properties in the parent class, and only implement validation in the parent class through the IValidatableObject interface.

C# Attribute - Revalidate when relational object is modified

I have a case where I in my project have activities, which can have a list of addresses, categories and other stuff.
In the site there is a requirement for the activity to have at least one address which is a "Visit address".
I have a ValidationAttribute (CheckStateAttribute) which triggers every time I make a direct modification of the activity. But it does not trigger when I modify an address connected to it.
[CheckState]
public partial class Activity
{
public Activity()
{
this.Address = new HashSet<Address>();
}
public int ID { get; set; }
public string Title { get; set; }
public virtual ICollection<Address> Address { get; set; }
}
An idea I have is to put the CheckState on the Address entity too (and of course make some changes to it), but since there are more requirements than just the address, it will not be a good solution to validate the activity from a lot of different entities.
Does anyone know a way to validate the entity "Activity" when any of its relational objects is modified?
You would inherit from IValidatableObject and implement the Validate method like below
[MetadataType(typeof(SeasonMetaData))]
public partial class Season : IValidatableObject
{
#region IValidatableObject Members
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (this.StartDate.CompareTo(this.EndDate) >= 0)
{
yield return new ValidationResult("The Season End must be after the Season Start.", new String[] { "EndDate" });
}
}
#endregion
}

Categories

Resources