I can validate Data Annotation and IValidatableObject when these one are on simple objects. However, in scenario where an object has a property that has to be validated, things get wrong.
public class BaseClass
{
public IEnumerable<ValidationResult> Validate()
{
var results = new List<ValidationResult>();
var validationContext = new ValidationContext(this, null, null);
Validator.TryValidateObject(this, validationContext, results, true);
return results;
}
}
public class Class1 : BaseClass, IValidatableObject
{
public Class1()
{
Property1 = new Class2();
}
public Class2 Property1 { get; set; }
//[Required]
public string AString1 { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var e = new ValidationResult("Error from class1");
var s = Property1.Validate();
var r = new List<ValidationResult>(s) { e };
return r;
}
}
public class Class2 :BaseClass, IValidatableObject
{
public Class2()
{
Property2 = new Class3();
}
public Class3 Property2 { get; set; }
//[Required]
public string AString2 { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
yield return new ValidationResult("Error from class2");
}
}
public class Class3:BaseClass
{
//[Required]
public string AString3 { get; set; }
}
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
var s = new Class1();
var results = s.Validate();
Assert.AreEqual(5, results.Count());
}
}
This small code snippet return 2 errors. The two that are from the Validate method of IValidatableObject. This is fine. However, if I uncomment the three "Required" data annotation I should have 5 errors (2 from Validate method and 3 froms Data Annotation).
Why when I uncomment the three data annotation that I have only one error "The AString1 field is required." which is the first class data annotation?
How can I have the five errors to be returned?
I believe there is because of the code in DataAnotations.Validator.GetObjectValidationErrors that kicks out after the first property error, short-circuiting the rest of validation rules. You can get around this by doing all validations inside Validate() method.
Related
I need to have different validation for the same object. So I thought to use Metadatatype to define the different rules the code is below:
public class ValidateObjectAttribute : ValidationAttribute
{
private readonly Type _validationMetaDataType;
public ValidateObjectAttribute(Type validationMetaDataType)
{
_validationMetaDataType = validationMetaDataType;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var modelType = value.GetType();
AssociatedMetadataTypeTypeDescriptionProvider associatedMetadataTypeTypeDescriptionProvider = null;
if (_validationMetaDataType != null)
{
associatedMetadataTypeTypeDescriptionProvider = new AssociatedMetadataTypeTypeDescriptionProvider(modelType, _validationMetaDataType);
TypeDescriptor.AddProvider(associatedMetadataTypeTypeDescriptionProvider, modelType);
}
var validationctx = new ValidationContext(value);
var results = new List<ValidationResult>();
Validator.TryValidateObject(value, validationctx, results, true);
if (associatedMetadataTypeTypeDescriptionProvider != null)
{
TypeDescriptor.RemoveProvider(associatedMetadataTypeTypeDescriptionProvider, modelType);
TypeDescriptor.Refresh(value);
}
if (results.Count == 0) return ValidationResult.Success;
return new ValidationResult($"Validation fail for prop: {validationContext.DisplayName}");
}
}
public class BarMetaData1
{
[Required]
public string BarField1;
[Required]
public string BarField2;
}
public class FooMetaData1
{
[Required]
public string FooField1;
[Required]
public string FooField2;
[Required, ValidateObject(typeof(BarMetaData1))]
public Bar FooObject1;
}
public class FooMetaData2
{
public string FooField1;
[Required]
public string FooField2;
[Required, ValidateObject(typeof(BarMetaData1))]
public Bar FooObject1;
}
public class BaseValidation
{
public bool IsValid(Type validationMetaDataType)
{
var modelType = this.GetType();
AssociatedMetadataTypeTypeDescriptionProvider associatedMetadataTypeTypeDescriptionProvider = null;
if (validationMetaDataType != null)
{
associatedMetadataTypeTypeDescriptionProvider = new AssociatedMetadataTypeTypeDescriptionProvider(modelType, validationMetaDataType);
TypeDescriptor.AddProvider(associatedMetadataTypeTypeDescriptionProvider, modelType);
TypeDescriptor.Refresh(this);
}
var validationctx = new ValidationContext(this);
var results = new List<ValidationResult>();
Validator.TryValidateObject(this, validationctx, results, true);
if (associatedMetadataTypeTypeDescriptionProvider != null)
{
TypeDescriptor.RemoveProvider(associatedMetadataTypeTypeDescriptionProvider, modelType);
}
return results.Count == 0;
}
}
public class Foo : BaseValidation
{
public string FooField1 { get; set; }
public string FooField2 { get; set; }
public Bar FooObject1 { get; set; }
}
public class Bar
{
public string BarField1 { get; set; }
public string BarField2 { get; set; }
}
the validation is call in this way:
public void Index()
{
Foo fooInstance = new Foo()
{
FooObject1 = new Bar()
};
fooInstance.IsValid(typeof(FooMetaData2));
fooInstance.IsValid(typeof(FooMetaData1));
Foo fooInstance2 = new Foo()
{
FooObject1 = new Bar()
};
fooInstance2.IsValid(typeof(FooMetaData2));
}
What is the issue:
first validation [fooInstance.IsValid(typeof(FooMetaData2))] is right (2 mandatory fields missing),
second validation call [fooInstance.IsValid(typeof(FooMetaData1))] the result is wrong (3 mandatory field missing but the code notify me only 2), it seems that the code apply the validation describe in FooMetaData2 class instead of FooMetaData1
Someone can explain to me why?
thanks
I solved using fluentValidator plugin
I am trying to print out an object that implements TableEntity class, without those that should be Ignored regarding the persistence. The approach I generally use to print out objects is to use the StatePrinter.
public class MyEntity : TableEntity
{
public string MyProperty { get; set; }
[IgnoreProperty]
public string MyIgnoredProperty { get; set; }
public override string ToString()
{
Stateprinter printer = new Stateprinter();
return printer.PrintObject(this);
}
}
While this works pretty good for any kind of classes, with this MyEntity class it also prints the MyIgnoredProperty. Is there a clever way to also ignore the properties that have [IgnoredProperty] as attribute when printing out the object?
You can configure what fields/properties the Stateprinter cares about by configuring what "field harvester" to use.
Here's a simple field harvester that only returns public properties without the 'IgnoreProperty' attribute.
class PersistencePropertiesHarvester : IFieldHarvester
{
public bool CanHandleType(Type type)
{
return typeof(TableEntity).IsAssignableFrom(type);
}
public List<SanitizedFieldInfo> GetFields(Type type)
{
var fields = new HarvestHelper().GetFieldsAndProperties(type);
return fields.Where(IsPerstistenceProperty).ToList();
}
private static bool IsPerstistenceProperty(SanitizedFieldInfo field)
{
return
// Only return properties ...
field.FieldInfo.MemberType == MemberTypes.Property
&&
// ... that has a public get method ...
(field.FieldInfo as PropertyInfo)?.GetGetMethod(false) != null
&&
// ... that does not have the IgnoreProperty attribute
field.FieldInfo.GetCustomAttribute<IgnoreProperty>() == null
;
}
}
Then you use it like this:
public class MyEntity : TableEntity
{
public string MyProperty { get; set; }
[IgnoreProperty]
public string MyIgnoredProperty { get; set; }
public override string ToString()
{
Stateprinter printer = new Stateprinter();
printer.Configuration.Add(new PersistencePropertiesHarvester());
return printer.PrintObject(this);
}
}
And the result of new MyEntity().ToString() is now
new MyEntity()
{
MyProperty = null
}
I am implementing a custom data annotation and in the main model it works correctly, but when I put the data annotation in the partial metada the validation does not find this data annotation.
my code is as follows:
public partial class register
{
public int id { get; set; }
public long idPerson { get; set; }
public string other { get; set; }
}
public partial class register_Metadata
{
[MyAttributeOne("val1")]
public int id { get; set; }
[MyAttributeOne("val1")]
public long idPerson { get; set; }
public string other { get; set; }
}
The namespace of the two classes is the same.
On the other hand I have a class where I link the two partial classes.
[MetadataType(typeof( register_Metadata))]
public partial class register
{
}
When I validate the field with customized metadata, the propierties function always has 0 results
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var properties = this.GetInvolvedProperties(validationContext.ObjectType);
var numberOfRequiredFields = RequireFromGroupAttribute.GetNumberOfRequiredFields(validationContext.ObjectType, this.Selector);
var values = new List<object> { value };
var otherPropertiesValues = properties.Where(p => p.Key.Name != validationContext.MemberName).Select(p => p.Key.GetValue(validationContext.ObjectInstance));
values.AddRange(otherPropertiesValues);
if (values.Count(s => !string.IsNullOrWhiteSpace(Convert.ToString(s))) >= numberOfRequiredFields)
{
return ValidationResult.Success;
}
return new ValidationResult(this.GetErrorMessage(numberOfRequiredFields, properties.Values), new List<string> { validationContext.MemberName });
}
private Dictionary<PropertyInfo, string> GetInvolvedProperties(Type type)
{
return type.GetProperties()
.Where(p => p.IsDefined(typeof(RequireFromGroupFieldAttribute)) &&
p.GetCustomAttribute<RequireFromGroupFieldAttribute>().Selector == this.Selector)
.ToDictionary(p => p, p => p.IsDefined(typeof(DisplayAttribute)) ? p.GetCustomAttribute<DisplayAttribute>().Name : p.Name);
}
I have changed the data annotation to the main class and then in the properties function I have the two parameters to evaluate.
However when I put them in the metadata class it does not work.
I have next (simplified) view model:
public class RegisterModel
{
public string UserName { get; set; }
[MustExistIf("SomeProperty", "some value", "SomeOtherProperty", ErrorMessage = "You have to select something")]
public string LastName { get; set; }
public AddressModel Address { get; set; }
}
public class AddressModel
{
public string Street { get; set; }
public string House { get; set; }
}
and I have custom validator
public class MustExistIfAttribute : ValidationAttribute, IClientValidatable
{
private string _masterName { get; set; }
private object _masterValue { get; set; }
private string _dependantName { get; set; }
public MustExistIfAttribute(string masterName, object masterValue, string dependantName)
{
this._masterName = masterName;
this._masterValue = masterValue;
this._dependantName = dependantName;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// get value of master property
var masValue = _getValue(validationContext.ObjectInstance, _masterName);
// get value of property whch depends on master property
var depValue = _getValue(validationContext.ObjectInstance, _dependantName);
if (masValue.Equals(_masterValue)) // if value in request is equal to value in specified in data annotation
{
if (depValue == null) // if dependant value does not exist
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
public override bool IsValid(object value)
{
return base.IsValid(value);
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var modelClientValidationRule = new ModelClientValidationRule
{
ValidationType = "mustexistif",
ErrorMessage = FormatErrorMessage(metadata.DisplayName)
};
modelClientValidationRule.ValidationParameters.Add("mastername", this._masterName);
modelClientValidationRule.ValidationParameters.Add("mastervalue", this._masterValue);
modelClientValidationRule.ValidationParameters.Add("dependantname", this._dependantName);
yield return modelClientValidationRule;
}
private static object _getValue(object objectInstance, string propertyName)
{
...
}
}
I have next javascript (please neglect returning false in mustexitif method - it's just for test purposes)
(function () {
jQuery.validator.addMethod('mustexistif', function (value, element, params) {
var masterName = params['mastername'];
var masterValue = params['mastervalue'];
var dependantName = params['dependantname'];
return false;
});
var setValidationValues = function (options, ruleName, value) {
options.rules[ruleName] = value;
if (options.message) {
options.messages[ruleName] = options.message;
}
};
var $Unob = $.validator.unobtrusive;
$Unob.adapters.add("mustexistif", ["mastername", "mastervalue", "dependantname"], function (options) {
var value = {
mastername: options.params.mastername,
mastervalue: options.params.mastervalue,
dependantname: options.params.dependantname
};
setValidationValues(options, "mustexistif", value);
});
})();
It works as expected when I decorate LastName property of RegisterModel class with MustExistIf annotation (like in provided code).
But what I really want is to decorate complex Address property of RegisterModel with MustExistIf annotation. Problem is that when I do that no unobrusive adapter gets registered (javascript doing that IS NOT triggered).
So, there is difference when I decoreate simple and complex properties. My solution does not allow me to decorate properties of Address class (FYI, I tried that and then also validation is working fine). Is there a way to accomplish what I intended? Am I missing something? Woud solution be to validate on model level? But then is it possible to do client side validation?
Maybe you can use Remote Validation.
http://msdn.microsoft.com/en-us/library/gg508808%28v=vs.98%29.aspx
I want to use AbstractValidator<T> inside base entity class.
[Serializable]
public abstract class Entity<T> where T : Entity<T>
{
public virtual Boolean Validate(AbstractValidator<T> validator)
{
return validator.Validate(this as ValidationContext<T>).IsValid;
}
// other stuff..
}
But one of my tests fails saying that Validate() method couldn't accept null as a paramter.
[Test]
public void CategoryDescriptionIsEmpty()
{
var category = new Category
{
Title = "some title",
Description = String.Empty
};
Assert.False(category.Validate(this.validator) == true);
}
[SetUp]
public void Setup()
{
this.validator = new CategoryValidator();
}
I'm using Visual Web Developer and at the moment can't install C# Developer Express to create console application to debug the error. Since that I don't know how do I debug inside the unit test. Alternatively it would be great if some explanation could be given!
Thanks!
This topic is old, but I found useful and made a little diferent:
public abstract class WithValidation<V> where V : IValidator
{
private IValidator v = Activator.CreateInstance<V>();
public bool IsValid => !(Errors.Count() > 0);
public IEnumerable<string> Errors
{
get
{
var results = v.Validate(this);
List<string> err = new List<string>();
if (!results.IsValid)
foreach (var item in results.Errors)
err.Add(item.ErrorMessage);
return err;
}
}
}
public class Client : WithValidation<ClientValidator>
{
public Guid Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
public class ClientValidator : AbstractValidator<Client>
{
public ClientValidator()
{
RuleFor(c => c.Name).NotNull();
RuleFor(c => c.Age).GreaterThan(10);
}
}
So you can use easier now like:
Client c = new Client();
var isvalid = c.IsValid;
IList<string> errors = c.Errors;
Ok!
So solution to my problem is next (at least this works as expected):
public abstract class Entity<T> where T : Entity<T>
{
public Boolean IsValid(IValidator<T> validator)
{
// var context = new ValidationContext(this);
// var instance = context.InstanceToValidate as T;
// return validator.Validate(instance).IsValid;
return validator.Validate(this as T).IsValid;
}
}
public class Rambo : Entity<Rambo>
{
public Int32 MadnessRatio { get; set; }
public Boolean CanHarmEverything { get; set; }
}
public class RamboValidator : AbstractValidator<Rambo>
{
public RamboValidator()
{
RuleFor(r => r.MadnessRatio).GreaterThan(100);
}
}
class Program
{
public static void Main(String[] args)
{
var ramboInstance = new Rambo {
MadnessRatio = 90
};
Console.WriteLine("Is Rembo still mad?");
Console.WriteLine(ramboInstance.IsValid(new RamboValidator()));
Console.ReadKey();
}
}