I need to compare two properties in a class using .net data annotations. One of the two properties should be filled and the other should be blank. How can I override the behavior of the CompareAttribute ? If it is not possible, what's the alternative solution ?
This class works with one issue:
If Property A is set to something and property B already has a value, then property A becomes invalid as expected. Upon Blanking property B, property A should become valid but it won't until I try to modify property A so I trigger the validation again. Is there a way to connect the two together to trigger the validation on both one either one changes ?
class CustomAttribute : ValidationAttribute
{
private readonly string _other;
public CustomAttribute(string other)
{
_other = other;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var property = validationContext.ObjectType.GetProperty(_other);
if (property == null)
{
return new ValidationResult(
string.Format("Unknown property: {0}", _other)
);
}
var otherValue = property.GetValue(validationContext.ObjectInstance, null);
if (!String.IsNullOrEmpty(value.ToString()) && !String.IsNullOrEmpty(otherValue.ToString()))
{
return new ValidationResult("Test");
}
return null;
}
}
For stuff like this I use ExpressiveAnnotations. It has a brilliant RequiredIf attribute:
[RequiredIf("B == null", ErrorMessage = "Either A or B should be filled")]
public string A { get; set; }
[RequiredIf("A == null", ErrorMessage = "Either A or B should be filled")]
public string B { get; set; }
You can extend the CompareAttribute with your own class:
public class CustomCompareAttribute: CompareAttribute {
public CustomCompareAttribute(string otherProperty) : base(otherProperty) {
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
if (OtherProperty == null && value == null) {
return new ValidationResult("Either A or B should be filled");
}
// more checks here ...
}
}
Related
Want to apply validation on input model using series of custom validation attributes as mentioned below.
If validation result of first validation attribute ie "ValidatorAttributeOne" is true than no need to process "ValidatorAttributeTwo" validation logic.
To achieve that valid result of "ValidationAttributeOne" assigned to "validationContext.Items" dictionary believing that "validationContext" will share across the different "ValidationAttributes" in same http request but below line always throws below exception
var isDependedFilterValidated = (bool?)validationContext.Items[dependedFilter]
"message": "The given key 'ValidationAttributeOne' was not present in the dictionary.",
public class ValidatorAttributeOne : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
//custom validation
validationContext.Items["ValidatorAttributeOne"] = true;
return ValidationResult.Success;
}
}
public class ValidatorAttributeTwo : ValidationAttribute
{
private readonly string dependedFilter = default(string);
public UsernamesEmailValidatorAttribute()
{
}
public UsernamesEmailValidatorAttribute(string filter)
{
dependedFilter = filter;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var isDependedFilterValidated = (bool?)validationContext.Items[dependedFilter];
if (isDependedFilterValidated == false)
{
//custom validation logic
}
return ValidationResult.Success;
}
}
public class CustomeModel
{
[ValidatorAttributeOne ]
[ValidatorAttributeTwo("ValidatorAttributeOne")]
public string usernames { get; set; }
}
Firstly,two custom validation attribute cannot share validationContext,if you don't want to do validationContext when the first one is true.You can set the contents of IsValid method of two validationAttributes together.
For example:
public class ValidationAttributeThree : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (ValidationAttributeOne IsValid method Content is true) {
return ValidationResult.Success;
}
else if(ValidationAttributeTwo IsValid method Content is true){
return new ValidationResult("ValidationAttributeOne error message");
}else{
return new ValidationResult("ValidationAttributeOne error message"+"ValidationAttributeTwo error message");
}
}
}
So I have checked out this answer ASP:NET MVC 4 dynamic validation of a property depending of the current value of another property and it does not cover the issue I am having.
I am using server side validation. I have a requirement that...
A value is only required if another property is specified
Issue
MVC binds each property and calls each validator on that property as it binds them. If I am dependent on multiple properties being set when I check validationContext.ObjectInstance.[MY_DEPENDENT_PROPERTY] there is a possibility that those dependent properties have not been bound yet.
What I need is a validation attribute that validates after binding - if that even exists.
So here is a simple example to explain my situation (not intended to be executed as it will more than likely be fine since the issue has to do with binding order)
My model
public class Address
{
[Required]
public string ResidentialAddress { get; set; }
public bool PostalIsTheSameAsResidential { get; set; }
// will only be required if PostalIsTheSameAsResidential is false.
// see the static method below and RequiredIfAttribute
[RequiredIf(typeof(Address), nameof(PostalRequiredIfNotSameAsResidential)]
public string PostalAddress { get; set; }
public static bool PostalRequiredIfNotSameAsResidential(Address model)
{
return !model.PostalIsTheSameAsResidential;
}
}
My validator
Essentially what happens here is it calls the static method on the model to see whether it should validate.
public sealed class RequiredIfAttribute : RequiredAttribute
{
private readonly MethodInfo _validationMethod;
public override bool RequiresValidationContext => true;
public RequiredIfAttribute(Type type, string methodName)
{
this._validationMethod = type.GetMethod(methodName);
if (this._validationMethod == null)
{
throw new MethodAccessException($"The validation method '{methodName}' does not exist on type '{type}");
}
}
public override bool IsValid(object value)
{
throw new NotSupportedException();
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ValidationResult result = ValidationResult.Success;
var parameters = this._validationMethod.GetParameters();
var returnType = this._validationMethod.ReturnType;
if (returnType == typeof(bool) && parameters.Length == 1 && parameters[0].ParameterType == validationContext.ObjectType)
{
if ((bool)_validationMethod.Invoke(null, new object[] { validationContext.ObjectInstance }))
{
if (!base.IsValid(value))
{
string[] memberNames;
if (validationContext.MemberName == null)
{
memberNames = null;
}
else
{
memberNames = new string[1];
memberNames[0] = validationContext.MemberName;
}
result = new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName), memberNames);
}
}
return result;
}
var expectedFuncType = typeof(Func<,>).MakeGenericType(validationContext.ObjectType, typeof(bool));
throw new MethodAccessException($"The validation method '{this._validationMethod}' does not have the correct definition. Expected '{expectedFuncType}'");
}
}
So this issue that I was having was that I was inheriting from the RequiredAttribute. Internally MVC handles this attribute differently to everything else.
When the Model Binder is looping through the properties, it gets the RequiredAttributes and executes them at the same time...
// System.Web.Mvc.DefaultModelBinder.SetProperty
....
ModelValidator modelValidator = (from v in ModelValidatorProviders.Providers.GetValidators(modelMetadata, controllerContext)
where v.IsRequired
select v).FirstOrDefault<ModelValidator>();
if (modelValidator != null)
{
foreach (ModelValidationResult current in modelValidator.Validate(bindingContext.Model))
{
bindingContext.ModelState.AddModelError(key, current.Message);
}
}
....
That v.IsRequired actually resolves to a line that tests if the current attribute is a RequiredAttribute and will validate it there, in the current, incomplete model state.
By inheriting from ValidationAttribute it ran the validations after the model had been built and solved my issue.
Thanks to #StephenMuecke for prompting me with this.
This is about Data Annotations in C#.
I have a class called MyClass. There I will be having two properties.
class MyClass
{
private string _propName;
public string PropName
{
get {return _propName;}
set {_propName=value;}
}
private string _propVal;
public string PropVal
{
get {return _propVal;}
set {_propVal=value;}
}
}
I have a MS SQl table which contains data for propName and relevant propValue.
I need to have validations using DataAnnotations. Each and every _propName need to have different validations.
My question is : How do I add different validations/data annotations to the class structure I have mentioned above ? I was wondering whether or not there is some way to specify if-else conditions in Data Annotations ?
if "this propertyname"
validation 1
else if "that property name"
validation 1
Can someone help ?
You should be able to something like this:
public class MyValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
switch (validationContext.MemberName)
{
//some logic
}
}
}
The problem is with ValidationAttribute your only going to get the value of the one property, not the combination of the properties, what you can do is combine the PropName and PropVal in a tuple, and then validate that.
This little test app shows a method of achieving what you want.
public class MyClass {
[MyValidation]
public Tuple<string, string> PropValue { get; set; }
public List<ValidationResult> TestValidation() {
var validationContext = new ValidationContext(this, serviceProvider: null, items: null);
var results = new List<ValidationResult>();
Validator.TryValidateObject(this, validationContext, results, validateAllProperties: true);
return results;
}
}
public class MyValidationAttribute : ValidationAttribute {
public override bool IsValid(object value) {
var propValue = value as Tuple<string, string>;
if (propValue.Item1 == "Item") {
var setPropValue = propValue.Item2;
//Do you validation
}
return true;
}
}
class Program {
static void Main(string[] args) {
MyClass instance = new MyClass();
instance.PropValue = new Tuple<string, string>("Item", "Value");
var result = instance.TestValidation();
}
}
I am trying to create my own validation attribute IsUnique that checks existing values for given property. I understand IsValid() must be overridden so that custom validation attribute can work. So far I have seen examples with validate attributes that take a string parameters which is then compared with hard coded values inside IsValid() method.
I need IsValid() method to get access to a property and its value to further compare it with values in the database.
This is what I have done so far:
public class IsUnique : ValidationAttribute
{
private string codeno { get; set; }
: base("{0} is already in use")
public IsUnique (string codeno)
{
this.codeno = codeno;
}
public override ValidationResult IsValid(object value,
ValidationContext vContext)
{
if (value != null)
{
MyDBContext db = new MyDBContext();
Student studentsCodeNo =
db.Students.FirstOrDefault(r => r.codeno== (string)value);
if (studentsCodeNo != null)
{
string errorMessage =
FormatErrorMessage(vContext.DisplayName);
return new ValidationResult(errorMessage);
}
}
return ValidationResult.Success;
}
}
As said, the problem is that this version takes parameter. I would like codeno to be read from a user form field, and such value would then be compared against anything in database. I don't know how to read values from the form fields.
Here is code
public class IsUnique : ValidationAttribute{
public override ValidationResult IsValid(object value,
ValidationContext vContext)
{
PropertyInfo property = validationContext.ObjectType.GetProperty("Codeno");
if (property == null)
return new ValidationResult(string.Format("Property '{0}' is undefined","Codeno"));
var fieldValue = property.GetValue(validationContext.ObjectInstance, null);
string codeno= (fieldValue == null ? "" : fieldValue.ToString());
if (!string.IsNullOrEmpty(codeno))
{
MyDBContext db = new MyDBContext();
Student studentsCodeNo =
db.Students.FirstOrDefault(r => r.codeno== codeno);
if (studentsCodeNo != null)
{
string errorMessage =
FormatErrorMessage(vContext.DisplayName);
return new ValidationResult(errorMessage);
}
}
return ValidationResult.Success; }}
There is sort of an out of the box way to do this already
http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.schema.indexattribute(v=vs.113).aspx
[Index(IsUnique=true)]
Is there a way to force the compiler to restrict the usage of a custom attribute to be used only on specific property types like int, short, string (all the primitive types)?
similar to the AttributeUsageAttribute's ValidOn-AttributeTargets enumeration.
No, you can't, basically. You can limit it to struct vs class vs interface, that is about it. Plus: you can't add attributes to types outside your code anyway (except for via TypeDescriptor, which isn't the same).
You can run this unit test to check it.
First, declare validation attribute PropertyType:
[AttributeUsage(AttributeTargets.Class)]
// [JetBrains.Annotations.BaseTypeRequired(typeof(Attribute))] uncomment if you use JetBrains.Annotations
public class PropertyTypeAttribute : Attribute
{
public Type[] Types { get; private set; }
public PropertyTypeAttribute(params Type[] types)
{
Types = types;
}
}
Create unit test:
[TestClass]
public class TestPropertyType
{
public static Type GetNullableUnderlying(Type nullableType)
{
return Nullable.GetUnderlyingType(nullableType) ?? nullableType;
}
[TestMethod]
public void Test_PropertyType()
{
var allTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes());
var allPropertyInfos = allTypes.SelectMany(a => a.GetProperties()).ToArray();
foreach (var propertyInfo in allPropertyInfos)
{
var propertyType = GetNullableUnderlying(propertyInfo.PropertyType);
foreach (var attribute in propertyInfo.GetCustomAttributes(true))
{
var attributes = attribute.GetType().GetCustomAttributes(true).OfType<PropertyTypeAttribute>();
foreach (var propertyTypeAttr in attributes)
if (!propertyTypeAttr.Types.Contains(propertyType))
throw new Exception(string.Format(
"Property '{0}.{1}' has invalid type: '{2}'. Allowed types for attribute '{3}': {4}",
propertyInfo.DeclaringType,
propertyInfo.Name,
propertyInfo.PropertyType,
attribute.GetType(),
string.Join(",", propertyTypeAttr.Types.Select(x => "'" + x.ToString() + "'"))));
}
}
}
}
Your attribute, for example allow only decimal property types:
[AttributeUsage(AttributeTargets.Property)]
[PropertyType(typeof(decimal))]
public class PriceAttribute : Attribute
{
}
Example model:
public class TestModel
{
[Price]
public decimal Price1 { get; set; } // ok
[Price]
public double Price2 { get; set; } // error
}
You could write code yourself to enforce correct use of your attribute class, but that's as much as you can do.
The code below will return an error if the attribute was placed on a property/field that is not List of string.
The line if (!(value is List<string> list)) may be a C#6 or 7 feature.
[AttributeUsage(AttributeTargets.Property |
AttributeTargets.Field, AllowMultiple = false)]
public sealed class RequiredStringListAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext context)
{
if (!(value is List<string> list))
return new ValidationResult($"The required attrribute must be of type List<string>");
bool valid = false;
foreach (var item in list)
{
if (!string.IsNullOrWhiteSpace(item))
valid = true;
}
return valid
? ValidationResult.Success
: new ValidationResult($"This field is required"); ;
}
}
The way I am doing this is following:
[AttributeUsage(AttributeTargets.Property)]
public class SomeValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value is not string stringToValidate)
{
throw new AttributeValueIsNotStringException(validationContext.DisplayName, validationContext.ObjectType.Name);
}
// validationContext.DisplayName is name of property, where validation attribut was used.
// validationContext.ObjectType.Name is name of class, in which the property is placed to instantly identify, where is the error.
//Some validation here.
return ValidationResult.Success;
}
}
And exception look like this:
public class AttributeValueIsNotStringException : Exception
{
public AttributeValueIsNotStringException(string propertyName, string className) : base(CreateMessage(propertyName, className))
{
}
private static string CreateMessage(string propertyName, string className)
{
return $"Validation attribute cannot be used for property: \"{propertyName}\" in class: \"{className}\" because it's type is not string. Use it only for string properties.";
}
}