How to force ValidationAttribute to mark specified object members as invalid? - c#

I've got my model which contains some members:
public class Address
{
public Street { get; set;}
public City { get; set; }
public PostalCode { get; set; }
}
Now I've got my ValidationAttribute with IsValid method overrided like this:
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var input = value as Address;
if (string.IsNullOrEmpty(input.City))
return new ValidationResult("City is required);
if (!string.IsNullOrEmpty(input.PostalCode))
if (string.IsNullOrEmpty(input.Street))
return new ValidationResult("Stret is required");
return ValidationResult.Success;
}
The problem is:
After validation my model state adds model error only to whole Adress member, but I need it to be added to specified members like city or street.
Any help with this will be appreciated... thanks!

you can add memberNames !
return new ValidationResult("City is required", new string[] { "City", "Street" });
EDIT :
i've tested and found a solution : System.ComponentModel.IDataErrorInfo !!
this doesn't work on the javascript client, but when submitting the result is what we attempted :)
so keep using the ValidationResult with memberNames
and :
public class myModel : System.ComponentModel.IDataErrorInfo
{
private Dictionary<string, List<ValidationResult>> errors;
private bool IsValidated = false;
private void Validate()
{
errors = new Dictionary<string, List<ValidationResult>>();
List<ValidationResult> lst = new List<ValidationResult>();
Validator.TryValidateObject(this, new ValidationContext(this, null, null), lst, true);
lst.ForEach(vr =>
{
foreach (var memberName in vr.MemberNames)
AddError(memberName, vr);
});
IsValidated = true;
}
private void AddError(string memberName, ValidationResult error)
{
if (!errors.ContainsKey(memberName))
errors[memberName] = new List<ValidationResult>();
errors[memberName].Add(error);
}
public string Error
{
get
{
if (!IsValidated)
Validate();
return string.Join("\n", errors
.Where(kvp => kvp.Value.Any())
.Select(kvp => kvp.Key + " : " + string.Join(", ", kvp.Value.Select(err => err.ErrorMessage)))
);
}
}
public string this[string columnName]
{
get
{
if (!IsValidated)
Validate();
if (errors.ContainsKey(columnName))
{
var value = errors[columnName];
return string.Join(", ", value.Select(err => err.ErrorMessage));
}
else
return null;
}
}
and now the current property is marked in error, and the memberNames too !
:D
Edit 2
In fact, you can keep simple DataAnnotations to inform client,
and server side, you can add more complex business validation :
public class myModel : IValidatableObject
{
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrEmpty(input.City))
yield return new ValidationResult("City is required",new string[]{"prop1","prop2"});
if (!string.IsNullOrEmpty(input.PostalCode))
if (string.IsNullOrEmpty(input.Street))
yield return new ValidationResult("Stret is required");
}
note that the Validate method is called only when DataAnnotations are all valid !

You need to use this constructor of ValidationResult class.
Eg
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var input = value as Address;
if (string.IsNullOrEmpty(input.City))
return new ValidationResult("City is required", new List<string>(){"City"});
if (!string.IsNullOrEmpty(input.PostalCode))
if (string.IsNullOrEmpty(input.Street))
return new ValidationResult("Stret is required", new List<string>(){"Street"});
return ValidationResult.Success;
}

Related

Custom client side validation attribute with parameter in ASP.NET Core using IClientModelValidator

I'm attempting to create my own client side validation attribute that would validate the property of a form on submit. I have been referencing the following Microsoft document: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-2.1#custom-validation.
I am unsure on how to add the validation rule to jQuery's validator object. This is how far I have gotten:
My ValidationAttribute is as follows
public class CannotEqualValue : ValidationAttribute, IClientModelValidator
{
private readonly string _value;
public CannotEqualValue(string value)
{
_value = value;
}
public void AddValidation(ClientModelValidationContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(
context.Attributes, "data-val-cannotbevalue", GetErrorMessage()); //???
MergeAttribute(
context.Attributes, "data-val-cannotbevalue-value", _value); //???
}
protected override ValidationResult IsValid(
object value,
ValidationContext validationContext)
{
var category = (Category) validationContext.ObjectInstance;
if (category.Name == _value)
return new ValidationResult(GetErrorMessage());
return ValidationResult.Success;
}
private bool MergeAttribute(
IDictionary<string, string> attributes,
string key,
string value)
{
if (attributes.ContainsKey(key)) return false;
attributes.Add(key, value);
return true;
}
private string GetErrorMessage()
{
return $"Name cannot be {_value}.";
}
}
The ValidationAttribute is used in a model like so
public class Category
{
[Key]
public int Id { get; set; }
[Required(ErrorMessage = "Name is required and must not be empty.")]
[StringLength(200, ErrorMessage = "Name must not exceed 200 characters.")]
[CannotEqualValue("Red")]
public string Name { get; set; }
}
I am referencing both jQuery validation and unobtrusive in my page.
I am unsure on how to add the rule to jQuery's validator object:
$.validator.addMethod("cannotbevalue",
function(value, element, parameters) {
//???
});
$.validator.unobtrusive.adapters.add("cannotbevalue",
[],
function(options) {
//???
});
Your MergeAttribute(..) lines of code in the AddValidation() method are correct and will add the data-val-* attributes for client side validation.
Your scripts need to be
$.validator.addMethod("cannotbevalue", function(value, element, params) {
if ($(element).val() == params.targetvalue) {
return false;
}
return true;
});
$.validator.unobtrusive.adapters.add('cannotbevalue', ['value'], function(options) {
options.rules['cannotbevalue'] = { targetvalue: options.params.value };
options.messages['cannotbevalue'] = options.message;
});

C# MVC5 Validate in Model with a List

Within a Class I have a Static List of values which are allowed
private static List<string> allowedClassNames = new List<string> {"Real Estate", "Factored Debt"};
And I also have an attribute of that class, which I want to restrict to being values in that list.
[Required]
public string assetClassName { get; set; }
I want to do this at the model level, so it works in either a REST or view context.
How would I implement forcing the value in the submission to be limited to that list?
Thanks!
Here's Where I wound up - Not fully tested yet, but to give an idea to future posters.
class MustContainAttribute : RequiredAttribute
{
public string Field { get; private set; }
List<string> allowed;
public MustContainAttribute(string validateField)
{
this.Field = validateField;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
switch (Field)
{
case "assetClassName":
allowed = new List<string> { "Real Estate", "Factored Debt" };
break;
default:
return ValidationResult.Success;
}
if (!allowed.Contains(Field))
{
return new ValidationResult("Invalid Value");
}else{
return ValidationResult.Success;
}
}
}
Create a custom validation attribute:
public class ClassNameRequiredAttribute : RequiredAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext context)
{
Object instance = context.ObjectInstance;
Type type = instance.GetType();
MyAssetClass myAssetClass = (MyAssetClass)type.GetProperty("MyAssetClass").GetValue(instance, null);
if (!string.IsNullOrEmpty(myAssetClass.assetClassName))
{
if (myAssetClass.allowedClassNames.Contains(myAssetClass.assetClassName))
{
return ValidationResult.Success;
}
}
return new ValidationResult(ErrorMessage);
}
}
And in your model:
[ClassNameRequired(ErrorMessage="Your error message.")]
public string assetClassName { get; set; }
As mentioned in the comments you can create your own ValidationAttribute. This is useful if you have this validation on multiple models or if you want to implement client side validation as well (JavaScript)
However, A quick and easy way to do one off validations like this is the IValidatableObject. You can use it as follows:
public class AssetModel:IValidatableObject
{
private static List<string> allowedClassNames = new List<string> {"Real Estate", "Factored Debt"};
[Required]
public string assetClassName { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!allowedClassNames.Contains(assetClassName)
{
yield new ValidationResult("Not an allowed value", new string[] { "assetClassName" } );
}
}
}

RequiredIf data annotation with enums

I have created a custom RequiredIf validator like this:
public class RequiredIfValidator : ValidationAttribute, IClientValidatable
{
RequiredAttribute _innerAttribute = new RequiredAttribute();
public string _dependentProperty { get; set; }
public object _targetValue { get; set; }
public RequiredIfValidator(string dependentProperty, object targetValue)
{
this._dependentProperty = dependentProperty;
this._targetValue = targetValue;
}
public override string FormatErrorMessage(string name)
{
return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, _dependentProperty);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var field = validationContext.ObjectInstance.GetType().GetProperty(_dependentProperty);
if (field != null)
{
var dependentValue = field.GetValue(validationContext.ObjectInstance, null);
if ((dependentValue == null && _targetValue == null) ||(dependentValue.Equals(_targetValue)))
{
if (!_innerAttribute.IsValid(value))
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule();
rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
rule.ValidationType = "requiredif";
rule.ValidationParameters["dependentproperty"] = _dependentProperty;
rule.ValidationParameters["targetvalue"] = _targetValue;
yield return rule;
}
}
I have an enum with various test types like this:
public enum TestTypes
{
Hair = 1,
Urine = 2
}
My ViewModel has some properties like this:
public class TestViewModel
{
public TestTypes TestTypeId {get; set;}
[RequiredIfValidator("TestTypeId", TestTypes.Hair)]
public string HairSpecimenId {get; set;}
}
My custom RequiredIfValidator is not working in this scinario. Is it because of the enum data type? Any way to achieve this with enums
You logic in the IsValid() does not appear to be correct. It should be
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
{
var otherProperty = validationContext.ObjectInstance.GetType().GetProperty(_dependentProperty);
var otherPropertyValue = otherProperty.GetValue(validationContext.ObjectInstance, null);
if (otherPropertyValue != null && otherPropertyValue.Equals(_targetValue ))
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}

Opposite of [compare(" ")] data annotation in .net?

What is the opposite/negate of [Compare(" ")] data annotation" in ASP.NET?
i.e: two properties must hold different values.
public string UserName { get; set; }
[Something["UserName"]]
public string Password { get; set; }
You can use the [NotEqualTo] data annotation operator included in MVC Foolproof Validation. I used it right now and it works great!
MVC Foolproof is an open source library created by #nick-riggs and has a lot of available validators. Besides doing server side validation it also does client side unobtrusive validation.
Full list of built in validators you get out of the box:
Included Operator Validators
[Is]
[EqualTo]
[NotEqualTo]
[GreaterThan]
[LessThan]
[GreaterThanOrEqualTo]
[LessThanOrEqualTo]
Included Required Validators
[RequiredIf]
[RequiredIfNot]
[RequiredIfTrue]
[RequiredIfFalse]
[RequiredIfEmpty]
[RequiredIfNotEmpty]
[RequiredIfRegExMatch]
[RequiredIfNotRegExMatch]
This is the implementation (server side) of the link that #Sverker84 referred to.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class UnlikeAttribute : ValidationAttribute
{
private const string DefaultErrorMessage = "The value of {0} cannot be the same as the value of the {1}.";
public string OtherProperty { get; private set; }
public UnlikeAttribute(string otherProperty)
: base(DefaultErrorMessage)
{
if (string.IsNullOrEmpty(otherProperty))
{
throw new ArgumentNullException("otherProperty");
}
OtherProperty = otherProperty;
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, OtherProperty);
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
if (value != null)
{
var otherProperty = validationContext.ObjectInstance.GetType()
.GetProperty(OtherProperty);
var otherPropertyValue = otherProperty
.GetValue(validationContext.ObjectInstance, null);
if (value.Equals(otherPropertyValue))
{
return new ValidationResult(
FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
}
Usage:
public string UserName { get; set; }
[Unlike("UserName")]
public string AlternateId { get; set; }
Details about this implementation, and how to implement it client-side can be found here:
http://www.devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-2
http://www.macaalay.com/2014/02/25/unobtrusive-client-and-server-side-not-equal-to-validation-in-mvc-using-custom-data-annotations/
The complete code for both server side and client side validation is as follows:
[AttributeUsage(AttributeTargets.Property)]
public class UnlikeAttribute : ValidationAttribute, IClientModelValidator
{
private string DependentProperty { get; }
public UnlikeAttribute(string dependentProperty)
{
if (string.IsNullOrEmpty(dependentProperty))
{
throw new ArgumentNullException(nameof(dependentProperty));
}
DependentProperty = dependentProperty;
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
if (value != null)
{
var otherProperty = validationContext.ObjectInstance.GetType().GetProperty(DependentProperty);
var otherPropertyValue = otherProperty.GetValue(validationContext.ObjectInstance, null);
if (value.Equals(otherPropertyValue))
{
return new ValidationResult(ErrorMessage);
}
}
return ValidationResult.Success;
}
public void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-unlike", ErrorMessage);
// Added the following code to account for the scenario where the object is deeper in the model's object hierarchy
var idAttribute = context.Attributes["id"];
var lastIndex = idAttribute.LastIndexOf('_');
var prefix = lastIndex > 0 ? idAttribute.Substring(0, lastIndex + 1) : string.Empty;
MergeAttribute(context.Attributes, "data-val-unlike-property", $"{prefix}{DependentProperty}");
}
private void MergeAttribute(IDictionary<string, string> attributes,
string key,
string value)
{
if (attributes.ContainsKey(key))
{
return;
}
attributes.Add(key, value);
}
}
Then include the following in JavaScript:
$.validator.addMethod('unlike',
function (value, element, params) {
var propertyValue = $(params[0]).val();
var dependentPropertyValue = $(params[1]).val();
return propertyValue !== dependentPropertyValue;
});
$.validator.unobtrusive.adapters.add('unlike',
['property'],
function (options) {
var element = $(options.form).find('#' + options.params['property'])[0];
options.rules['unlike'] = [element, options.element];
options.messages['unlike'] = options.message;
});
Usage is as follows:
public int FromId { get; set; }
[Unlike(nameof(FromId), ErrorMessage = "From ID and To ID cannot be the same")]
public int ToId { get; set; }
Use this in your get/set logic:
stringA.Equals(stringB) == false
In addition to solution given by #Eitan K, If you want to use other property's display name instead of other property's name, use this snippet:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class UnlikeAttribute : ValidationAttribute
{
private const string DefaultErrorMessage = "The value of {0} cannot be the same as the value of the {1}.";
public string OtherPropertyDisplayName { get; private set; }
public string OtherProperty { get; private set; }
public UnlikeAttribute(string otherProperty)
: base(DefaultErrorMessage)
{
if (string.IsNullOrEmpty(otherProperty))
{
throw new ArgumentNullException("otherProperty");
}
OtherProperty = otherProperty;
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, OtherPropertyDisplayName);
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
if (value != null)
{
var otherProperty = validationContext.ObjectInstance.GetType()
.GetProperty(OtherProperty);
var otherPropertyValue = otherProperty
.GetValue(validationContext.ObjectInstance, null);
if (value.Equals(otherPropertyValue))
{
OtherPropertyDisplayName = otherProperty.GetCustomAttribute<DisplayAttribute>().Name;
return new ValidationResult(
FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
}

How to create a custom attribute in Data Annotaion

How I can create custom attribute in Data Annotation?I want to set Control name assosiated with a property ad I don't find any suitable attribute.
How I can do this?
thanks
You must extend System.ComponentModel.DataAnnotations.ValidationAttribute
K. Scott Allen (of OdeToCode) has a great example where he builds a custom "GreaterThan" attribute.
http://odetocode.com/blogs/scott/archive/2011/02/21/custom-data-annotation-validator-part-i-server-code.aspx
Here is the snippet included inline though:
public class GreaterThanAttribute : ValidationAttribute
{
public GreaterThanAttribute(string otherProperty)
:base("{0} must be greater than {1}")
{
OtherProperty = otherProperty;
}
public string OtherProperty { get; set; }
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, OtherProperty);
}
protected override ValidationResult
IsValid(object firstValue, ValidationContext validationContext)
{
var firstComparable = firstValue as IComparable;
var secondComparable = GetSecondComparable(validationContext);
if (firstComparable != null && secondComparable != null)
{
if (firstComparable.CompareTo(secondComparable) < 1)
{
return new ValidationResult(
FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
protected IComparable GetSecondComparable(
ValidationContext validationContext)
{
var propertyInfo = validationContext
.ObjectType
.GetProperty(OtherProperty);
if (propertyInfo != null)
{
var secondValue = propertyInfo.GetValue(
validationContext.ObjectInstance, null);
return secondValue as IComparable;
}
return null;
}
}

Categories