I create my own UniqueAttribute validation which check data in DB.
Problem is how to ignore own row.
So example:
Current email address: test#test.com Current address: Address1
now I change
Current address: Address2 Current email address: test#test.com ->
stays the same
I get message that this email already exist. How to remember old value?
Code:
#region
using Agado.Jobs.Infrastructure.ExtensionMethods;
using Agado.Jobs.StudentService.Web.Infrastructure.Utils;
using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Reflection;
#endregion
namespace Agado.Jobs.StudentService.Web.Infrastructure.Validators
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class UniqueAttribute : ValidationAttribute
{
public UniqueAttribute(Type dataContextType, Type entityType, string propertyName, string methodName)
{
DataContextType = dataContextType;
EntityType = entityType;
PropertyName = propertyName;
MethodName = methodName;
}
public Type DataContextType { get; private set; }
public Type EntityType { get; private set; }
public string PropertyName { get; private set; }
public string MethodName { get; private set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null) return ValidationResult.Success;
var idValue = ValidationUtils.GetValue<int>(validationContext.ObjectInstance, "Id");
var repository = Jobs.Infrastructure.IoC.Resolve(DataContextType);
PropertyInfo propertyInfo = EntityType.GetProperty(PropertyName);
if (propertyInfo == null)
{
throw new Exception("PropertyInfo with name '{0}' is NULL".FormatWith(PropertyName));
}
var propertyType = propertyInfo.PropertyType;
if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
propertyType = Nullable.GetUnderlyingType(propertyType);
}
var args = new[] { Convert.ChangeType(value, propertyType) };
var data = repository.GetType().InvokeMember(MethodName, BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public, null, repository, args);
if (idValue != 0 && data != null)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
else if (idValue == 0 && data != null)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
else
{
return new RequiredAttribute().IsValid(value) ? ValidationResult.Success : new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
return ValidationResult.Success;
}
public override string FormatErrorMessage(string name)
{
if (String.IsNullOrEmpty(ErrorMessage))
{
var genericMessageWithPlaceHolder = (string)Resources.ResourceManager.GetObject("PropertyUniqueDefaultError");
if (!string.IsNullOrEmpty(genericMessageWithPlaceHolder))
{
ErrorMessage = genericMessageWithPlaceHolder;
}
}
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString, name);
}
}
}
Use:
[Unique(typeof(ICustomerRepository), typeof(customers), "Email", "GetByEmail")]
[Display(Name = Translations.Global.EMAIL)]
public string Email { get; set; }
Related
I would need help to ensure CustomerCode has value only if UserTypeID is not 1
public class FilteringViewModel
{
[Required]
public int? UserID { get; set; }
[Required]
public string UserTypeID { get; set; }
[RequiredIf("UserTypeID", "1")]
public string EmployeeCode { get; set; }
[RequiredIf("UserTypeID", "!1")]
public string CustomerCode { get; set; }
}
public class RequiredIfAttribute : ValidationAttribute
{
RequiredAttribute _innerAttribute = new RequiredAttribute();
public string _dependentProperty { get; set; }
public object _targetValue { get; set; }
public RequiredIfAttribute(string dependentProperty, object targetValue)
{
this._dependentProperty = dependentProperty;
this._targetValue = targetValue;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var field = validationContext.ObjectType.GetProperty(_dependentProperty);
if (field != null)
{
var dependentValue = field.GetValue(validationContext.ObjectInstance, null);
if ((dependentValue == null && _targetValue == null) || (dependentValue.Equals(_targetValue)))
{
if (!_innerAttribute.IsValid(value))
{
string name = validationContext.DisplayName;
string specificErrorMessage = ErrorMessage;
if (string.IsNullOrEmpty(specificErrorMessage))
specificErrorMessage = $"{name} is required.";
return new ValidationResult(specificErrorMessage, new[] { validationContext.MemberName });
}
}
return ValidationResult.Success;
}
else
{
return new ValidationResult(FormatErrorMessage(_dependentProperty));
}
}
}
EmployeeCode is required if UserTypeID is 1 (this part is working fine)
CustomerCode is required if UserTypeID is not 1 (this part is not working)
As per explain by Yong Shun, the logic does not affected by "!". I have change the IsValid() function as per below and now its working:
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var field = validationContext.ObjectType.GetProperty(_dependentProperty);
if (field != null)
{
var dependentValue = field.GetValue(validationContext.ObjectInstance, null);
if (
(dependentValue == null && _targetValue == null)
|| (!_targetValue.ToString().StartsWith("!") && dependentValue.Equals(_targetValue))
|| (_targetValue.ToString().StartsWith("!") && !dependentValue.Equals(_targetValue.ToString().Substring(1)))
)
{
//this statement means that we will proceed to do the validation
if (!_innerAttribute.IsValid(value))
{
string name = validationContext.DisplayName;
string specificErrorMessage = ErrorMessage;
if (string.IsNullOrEmpty(specificErrorMessage))
specificErrorMessage = $"{name} is required.";
return new ValidationResult(specificErrorMessage, new[] { validationContext.MemberName });
}
}
return ValidationResult.Success;
}
else
{
return new ValidationResult(FormatErrorMessage(_dependentProperty));
}
}
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;
}
In ColorSettings model shown below, I'm able to validate property MyColor with EnumDataType to make sure that only the value defined in ColorsEnum can be entered as shown below. This works fine for a single string value of type ColorsEnum.
[Test]
public void ColorSettingsModel_MyColorEnum_Invalid()
{
var sut = GetColorSettingsModel();
sut.MyColor = "wrong color value";
var context = new ValidationContext(sut, null, null);
var validationResults = new List<ValidationResult>();
var isModelStateValid = Validator.TryValidateObject(sut, context, validationResults, true);
Assert.AreEqual(1, validationResults.Count);
Assert.IsTrue(validationResults.Any(x => x.ErrorMessage == "MyColorEnum"));
}
How can I validate the field SelectedColors using a similar method that contains an array of type ColorsEnum? Is there a way to apply EnumDataType attribute to an array of type ColorsEnum?
public enum ColorsEnum
{
red,
blue,
green
}
public class ColorSettings
{
[Required(ErrorMessage = "MyColorRequired")]
[EnumDataType(typeof(ColorsEnum), ErrorMessage = "MyColorEnum")]
public string MyColor { get; set; }
[Required(ErrorMessage = "SelectedColorsRequired")]
// How to validate an array of type ColorsEnum with EnumDataType ?
public string[] SelectedColors { get; set; }
}
You can define a custom attribute like the one below :
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = false)]
public sealed class EnumDataTypeArrayAttribute : DataTypeAttribute
{
public EnumDataTypeArrayAttribute(Type enumType)
: base("Enumeration")
{
if (enumType == null)
{
throw new InvalidOperationException("Type cannot be null");
}
if (!enumType.IsEnum)
{
throw new InvalidOperationException("Type must be an enum");
}
this.EnumType = enumType;
}
public override bool IsValid(object value)
{
if (value == null) return true;
var at = value.GetType();
if (!at.IsArray) return false;
var t = at.GetElementType();
if (t != this.EnumType) return false;
foreach (var v in value as Array)
{
if (!Enum.IsDefined(this.EnumType, v))
{
return false;
}
}
return true;
}
public Type EnumType
{
get;
private set;
}
}
Then annotate your property or whatever you want to validate like that :
[EnumDataTypeArray(typeof(SomeEnumType))]
public SomeEnumType[] Types { get; set; }
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;
}
}
Does anybody know of a good algorithm to mutually exclusively check two properties using a ModelValidator?
Something like:
[EitherPropertyRequired("BuildingNumber","BuildingName"]
public class Address{
public int BuildingNumber { get; set; }
public string BuildingName { get; set; }
}
I ended up creating an attribute and manually checking it with a custom ModelValidator. This custom model validator is checked using an AssociatedValidatorProvider which is registered in Application_Start().
protected void Application_Start()
{
ModelValidatorProviders.Providers.Add(new ZipValidationProvider());
}
public class ZipValidationProvider:AssociatedValidatorProvider
{
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
foreach (var attribute in attributes.OfType<EitherPropertyRequiredAttribute>())
{
yield return new EitherPropertyRequiredValidator(metadata,
context, attribute.FirstProperty, attribute.SecondProperty, attribute.Message);
}
}
}
[AttributeUsage(AttributeTargets.Class)]
public class EitherPropertyRequiredAttribute : Attribute
{
public readonly string FirstProperty;
public readonly string SecondProperty;
public readonly string Message;
public EitherPropertyRequiredAttribute(string firstProperty, string secondProperty,
string message)
{
FirstProperty = firstProperty;
SecondProperty = secondProperty;
Message = message;
}
}
public class EitherPropertyRequiredValidator:ModelValidator
{
private readonly string firstProperty;
private readonly string secondProperty;
private readonly string message;
public EitherPropertyRequiredValidator(ModelMetadata metadata,
ControllerContext context,
string firstProperty,
string secondProperty,
string message)
:base(metadata,context)
{
this.firstProperty = firstProperty;
this.secondProperty = secondProperty;
this.message = message;
}
private PropertyInfo GetPropertyInfoRecursive(Type type, string property)
{
var prop = type.GetProperty(property);
if (prop != null) return prop;
foreach (var p in type.GetProperties())
{
if (p.PropertyType.Assembly == typeof (object).Assembly)
continue;
return GetPropertyInfoRecursive(p.PropertyType, property);
}
return null;
}
private object GetPropertyValueRecursive(object obj, PropertyInfo propertyInfo)
{
Type objectType = obj.GetType();
if(objectType.GetProperty(propertyInfo.Name) != null)
return propertyInfo.GetValue(obj, null);
foreach (var p in objectType.GetProperties())
{
if (p.PropertyType.Assembly == typeof(object).Assembly)
continue;
var o = p.GetValue(obj,null);
return GetPropertyValueRecursive(o, propertyInfo);
}
return null;
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
if (Metadata.Model == null)
yield break;
var firstPropertyInfo = GetPropertyInfoRecursive(Metadata.Model.GetType(),firstProperty);
if(firstPropertyInfo == null)
throw new InvalidOperationException("Unknown property:" + firstProperty);
var secondPropertyInfo = GetPropertyInfoRecursive(Metadata.Model.GetType(),secondProperty);
if(secondPropertyInfo == null)
throw new InvalidOperationException("Unknown property:" + secondProperty);
var firstPropertyValue = GetPropertyValueRecursive(Metadata.Model, firstPropertyInfo);
var secondPropertyValue = GetPropertyValueRecursive(Metadata.Model, secondPropertyInfo);
bool firstPropertyIsEmpty = firstPropertyValue == null ||
firstPropertyValue.ToString().Length == 0;
bool secondPropertyIsEmpty = secondPropertyValue == null ||
secondPropertyValue.ToString().Length == 0;
if (firstPropertyIsEmpty && secondPropertyIsEmpty)
{
yield return new ModelValidationResult
{
MemberName = firstProperty,
Message = message
};
}
}
}
[AttributeUsage(AttributeTargets.Class)]
public class EitherPropertyRequiredAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
// value will be the model
Address address = (Address)value;
// TODO: Check the properties of address here and return true or false
return true;
}
}
You could make this more generic by avoiding it casting to Address and using attribute properties and reflection.