I currently have a custom ValidationAttribute that ensures that a property is unique. It looks like this:
public class UniqueLoginAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Context db = new Context();
if (db.Users.SingleOrDefault(user => user.Login == (string)value) != null)
{
return new ValidationResult(validationContext.DisplayName + " is already taken.");
}
return null;
}
}
}
What I'd like to is make this Validation work with any Entity/Property combination. In other words, set both the Entity (in this case "Users") and Property (in this case "Login"), at runtime. I've found some examples of DynamicLINQ, but I'd like a pure EF solution, which I can't seem to find.
This will work. You need to build the expression tree manually.
Usage
Expression<Func<User, string>> userExp = x => x.Login;
UniqueAttribute u = new UniqueAttribute(userExp);`
EDIT: Removed generics to work w/ Attribute. You need to use reflection w/ the runtime types to get the appropriate SingleOrDefault method. Please note there is no compile time type checking on your expressions with this code. You should always declare the expression first (the way I did in the usage sample) to avoid type problems.
public class UniqueAttribute
{
private LambdaExpression Selector { get; set; }
private Type EntityType { get; set; }
public UniqueAttribute(LambdaExpression selector) {
this.EntityType = selector.Parameters[0].Type;
this.Selector = selector;
}
private LambdaExpression GeneratePredicate(object value) {
ParameterExpression param = Selector.Parameters[0];
Expression property = Selector.Body;
Expression valueConst = Expression.Constant(value);
Expression eq = Expression.Equal(property, valueConst);
LambdaExpression predicate = Expression.Lambda(eq, new ParameterExpression[]{param});
return predicate;
}
private TEntity SingleOrDefault<TEntity>(IQueryable<TEntity> set, LambdaExpression predicate) {
Type queryableType = typeof(Queryable);
IEnumerable<MethodInfo> allSodAccessors = queryableType.GetMethods(BindingFlags.Static | BindingFlags.Public).Where(x => x.Name=="SingleOrDefault");
MethodInfo twoArgSodAccessor = allSodAccessors.Single(x => x.GetParameters().Length == 2);
MethodInfo withGenArgs = twoArgSodAccessor.MakeGenericMethod(new []{typeof(TEntity)});
return (TEntity) withGenArgs.Invoke(null, new object[]{set, predicate});
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Context db = new Context();
if (SingleOrDefault(db.Set(EntityType), GeneratePredicate(value)) != null)
{
return new ValidationResult(validationContext.DisplayName + " is already taken.");
}
return null;
}
}
This may work for you, but has the downside of needing to retrieve the full list of entities and loop through them to look for a match. You can specify the entity and property as arguments to the attribute.
public class UniqueAttribute : ValidationAttribute
{
public UniqueAttribute(Type entityType, string propertyName)
{
_entityType = entityType;
_propertyName = propertyName;
}
private Type _entityType;
private string _propertyName;
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Context db = new Context();
foreach (var item in db.Set(_entityType).ToList())
{
if (value.Equals(GetPropertyValue(item, _propertyName))
{
return new ValidationResult(validationContext.DisplayName + " is already taken.");
}
}
return null;
}
private object GetPropertyValue(object item, string propertyName)
{
var type = item.GetType();
var propInfo = type.GetProperty(propertyName);
return (propInfo != null) ? propInfo.GetValue(value, null) : null;
}
}
}
Usage:
[Unique(typeof(User), "Login")]
public string Login { get; set; }
Related
I use ListValidation
public class Test
{
[ListValidation(ErrorMessage ="wrong")]
public List<string> Listt { get; set; }
}
ListValidation implementation
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class ListValidationAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
var list = value as IList;
if (list != null)
{
return list.Count > 0;
}
return false;
}
}
when I test it
Test t = new Test();
List<string> str = new List<string>();
str.Add("haha");
str.Add("hoho");
t.Listt = str;
JsonResult json = ModelValidation.ValidateProperty(t, nameof(t.Listt));
It throws ArgumentException
{System.ArgumentException: The value for property 'Listt' must be of type 'System.Collections.Generic.List`1[System.String]'.
Parameter name: value
at System.ComponentModel.DataAnnotations.Validator.EnsureValidPropertyType(String propertyName, Type propertyType, Object value)
at System.ComponentModel.DataAnnotations.Validator.TryValidateProperty(Object value, ValidationContext validationContext, ICollection`1 validationResults)
at EArchive.Infrastructure.ModelValidation.ValidateProperty(Object obj, String property) in C:\Users\haha\ModelValidation.cs:line 54}
ValidateProperty implementation
public static JsonResult ValidateProperty(object obj, string property)
{
ValidationContext context = new ValidationContext(obj)
{
MemberName = property
};
List<ValidationResult> results = new List<ValidationResult>();
bool valid = Validator.TryValidateProperty(property, context, results);
if (!valid) // there is no error and everything is good
{
return null;
}
string errors = "";
// fetch all errors happened in the property.
foreach (ValidationResult result in results)
{
errors += result.ErrorMessage + "\n <br>";
}
Dictionary<string, string> err = new Dictionary<string, string>()
{
{ "status", "fail" },
{ "message", errors }
};
return new JsonResult(err);
}
What's wrong here?
Validator.TryValidateProperty expects the first argument (object value) to be the value to test for the property rather than the name of it. In your example, you are passing the string Listt instead of the value of t.Listt. In order to get this to work for your purposes, you'll need to change your ValidateProperty function, as follows:
public static JsonResult ValidateProperty(object propertyValue, string propertyName, object sourceObject)
{
ValidationContext context = new ValidationContext(sourceObject)
{
MemberName = propertyName
};
List<ValidationResult> results = new List<ValidationResult>();
bool valid = Validator.TryValidateProperty(propertyValue, context, results);
// ...
Then, just update your call-site accordingly:
JsonResult json = ModelValidation.ValidateProperty(t.Listt, nameof(t.Listt), t);
Here's a blog post I used as inspiration for this answer: https://gigi.nullneuron.net/gigilabs/simple-validation-with-data-annotations/.
Somewhere in the internet I found below class which I am using to convert Expression<Func<T, bool>> from DTO to Domain:
public class EvaluateVariableVisitor<TEntity, TDto> : ExpressionVisitor
{
private readonly ParameterExpression _dtoParameter;
private readonly ParameterExpression _entityParameter;
private readonly IMapper _mapper;
public EvaluateVariableVisitor()
{
_entityParameter = Expression.Parameter(typeof(TEntity));
_dtoParameter = Expression.Parameter(typeof(TDto));
_mapper = AutoMapperConfig.Initialize();
}
protected override Expression VisitMember(MemberExpression node)
{
try
{
//change dto to entity type.
if (node.Expression.Type == _dtoParameter.Type)
{
var reducedExpression = Visit(node.Expression);
//TypeMap typeMap = Mapper.Configuration.FindTypeMapFor<TDto, TEntity>();
TypeMap typeMap = _mapper.ConfigurationProvider.FindTypeMapFor<TDto, TEntity>();
//find the correct name of the property in the destination object by using the name of the source objekt
string destinationPropertyName = typeMap.GetPropertyMaps() //GetCustomPropertyMaps()
.Where(propertyMap => propertyMap.SourceMember.Name == node.Member.Name)
.Select(propertyMap => propertyMap.DestinationProperty.Name).FirstOrDefault();
//find the correct name of the property in the destination object by using the name of the source objekt
//string destinationPropertyName = typeMap.GetPropertyMaps() //GetCustomPropertyMaps()
// .Where(propertyMap => propertyMap.SourceMember.Name == node.Member.Name)
// .Select(propertyMap => propertyMap.DestinationProperty.Name).Single();
var newMember = _entityParameter.Type.GetMember(destinationPropertyName).First();
return Expression.MakeMemberAccess(reducedExpression, newMember);
}
//Recurse down to see if we can simplify...
var expression = Visit(node.Expression);
//If we've ended up with a constant, and it's a property or a field,
//we can simplify ourselves to a constant
var constantExpression = expression as ConstantExpression;
if (constantExpression != null)
{
object container = constantExpression.Value;
object value = null;
var memberAsFieldInfo = node.Member as FieldInfo;
var memberAsPropertyInfo = node.Member as PropertyInfo;
if (memberAsFieldInfo != null)
{
value = memberAsFieldInfo.GetValue(container);
}
if (memberAsPropertyInfo != null)
{
value = memberAsPropertyInfo.GetValue(container, null);
}
if (value != null)
{
return Expression.Constant(value);
}
}
return base.VisitMember(node);
}
catch (System.Exception exc)
{
var ex = exc.Message;
throw;
}
}
//change type from dto to entity --> otherwise the generated expression tree would throw an exception, because of missmatching types(Dto can't be used in Entity expression).
protected override Expression VisitParameter(ParameterExpression node)
{
return node.Type == _dtoParameter.Type ? _entityParameter : node;
}
}
It was working great but I faced an issue when I had property of the same type inside my DTO:
public class InventoryApplicationDto
{
public int Id { get; set; }
public string Name{ get; set; }
public InventoryApplicationDto ParentApplication { get; set; }
}
First of all I had to change:
string destinationPropertyName = typeMap.GetPropertyMaps()
.Where(propertyMap => propertyMap.SourceMember.Name == node.Member.Name)
.Select(propertyMap => propertyMap.DestinationProperty.Name).Single();
To:
string destinationPropertyName = typeMap.GetPropertyMaps()
.Where(propertyMap => propertyMap.SourceMember.Name == node.Member.Name)
.Select(propertyMap => propertyMap.DestinationProperty.Name).FirstOrDefault();
My problem is that I am getting error:
System.ArgumentException: Property 'Int32 ID' is not defined for type
'InventoryApplicationDto'
on line:
return Expression.MakeMemberAccess(reducedExpression, newMember);
Is MakeMemberAccess method case sensitive or there is another issue?
I am trying to copy the behavior of Entity Framework in creating the query from expression and i found my way using ExpressionVisitor when getting the property of the model by using Attribute
this is what i got so far
internal class NVisitor : ExpressionVisitor
{
private readonly ParameterExpression _parameter;
private readonly Type _type;
public NVisitor(Type type)
{
_type = type;
_parameter = Expression.Parameter(type);
}
protected override Expression VisitParameter(ParameterExpression node)
{
return _parameter;
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Member.MemberType == MemberTypes.Property)
{
var memberName = node.Member.Name;
PropertyInfo otherMember = _type.GetProperty(memberName);
var ncols = node.Member.GetCustomAttributes(typeof(NColumn), true);
if (ncols.Any())
{
var ncol = (NColumn)ncols.First();
otherMember = _type.GetProperty(ncol.Name);
}
var inner = Visit(node.Expression);
return Expression.Property(inner, otherMember);
}
return base.VisitMember(node);
}
}
i have an attribute NColumn that indicates the real name of the property from table column so i mark the property of the model by Attribute
public class BonusTypeX
{
[NColumn("BonusTypeName")]
public string Name { get; set; }
}
now when im trying to get the expression,
[TestMethod]
public void ExpressionTesting2()
{
string searchKey = "Xmas";
Expression<Func<BonusTypeX, bool>> expression = x => x.Name.Contains(searchKey);
Type t = typeof(tbl_BonusType);
var body = new NVisitor(t).Visit(expression.Body);
string a = string.Join(".", body.ToString().Split('.').Skip(1));
Assert.AreEqual("BonusTypeName.Contains(\"Xmas\")", a);
}
i got this
BonusTypeName.Contains(value(Payroll.Test.Administration.TestRepositories+<>c__DisplayClass13).searchKey)
what i am expecting to get is
BonusTypeName.Contains("Xmas")
is there any method that gets the expression string? i am using
string a = string.Join(".", body.ToString().Split('.').Skip(1));
which i think it might be wrong.. :)
any help would be appreciated.
Local variable are captured in a compiler generated class at runtime, this explains the Payroll.Test.Administration.TestRepositories+<>c__DisplayClass13).searchKey part. To get the generated field's value in your expression you must explicitly replace it's value when visiting the expression:
protected override Expression VisitMember(MemberExpression node)
{
if (node.Member.MemberType == MemberTypes.Property)
{
var memberName = node.Member.Name;
PropertyInfo otherMember = _type.GetProperty(memberName);
var ncols = node.Member.GetCustomAttributes(typeof(NColumn), true);
if (ncols.Any())
{
var ncol = (NColumn)ncols.First();
otherMember = _type.GetProperty(ncol.Name);
}
var inner = Visit(node.Expression);
return Expression.Property(inner, otherMember);
}
if (node.Member.MemberType == MemberTypes.Field)
{
if (node.Expression is ConstantExpression)
{
var owner = ((ConstantExpression)node.Expression).Value;
var value = Expression.Constant(((FieldInfo)node.Member).GetValue(owner));
return value;
}
}
return base.VisitMember(node);
}
I want to write custom validation attribute and add additional member names which have validation errors to validation result. The thing is I want to generate member name dynamically based on property name and invalid match property index or key (I want to validate IEnumerables or IDictionaries) like Names[0], Names[1], Names[key] etc. For example:
Model:
public class ModelClass
{
[ItemMaxLength(10)]
[Display(ResourceType = typeof(CategoriesRename), Name = "CategoryNamesFieldName")]
public IDictionary<string, string> Names { get; set; }
}
Attribute:
public class ItemMaxLengthAttribute : ValidationAttribute
{
private readonly int _maxLength = int.MaxValue;
public ItemMaxLengthAttribute(int maxLength)
{
_maxLength = maxLength;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
...
// I can get instance and it's type from validation context
var instance = validationContext.ObjectInstance; // which is instance of ModelClass
var instanceType = validationContext.ObjectType; //which is typeof(ModelClass)
var dispayName = validationContext.DisplayName; //which is value of Display attribute
...
}
}
So the main idea is (I don't like it ether) get current property been validated by it's DysplayName attribute value (dispayName). I'm kind'a stuck here for a while. Maybe is there some other way to get property info of the property which is validating?
P.S. I've already tried MemberName property, as Alexandre Rondeau suggested, but the problem is that validationContext.MemberName = null so it can't be used. Also MSDN says that this property represents an entity member name, not the name of a corresponding data field and I need the name of a corresponding data field.
Using that code, both test passes, so the MemberName isn't null.
[TestClass]
public class RefectionInValidationTest
{
[TestMethod]
public void GivenAModelWithItemMaxAttributeOnFieldName_WhenValidating_ThenModelClassIsValid()
{
//Arange
var validModelClass = new ModelClass();
var validations = new Collection<ValidationResult>();
//Act
var isValid = Validator.TryValidateObject(validModelClass, new ValidationContext(validModelClass, null, null), validations, true);
//Assert
Assert.IsTrue(isValid);
}
[TestMethod]
public void GivenAModelWithItemMaxAttributeOnFieldNotName_WhenValidating_ThenModelClassIsInvalid()
{
//Arange
var invalidaModelClass = new InvalidModelClass();
var validations = new Collection<ValidationResult>();
//Act
var isValid = Validator.TryValidateObject(invalidaModelClass, new ValidationContext(invalidaModelClass, null, null), validations, true);
//Assert
Assert.IsFalse(isValid);
}
}
public class ModelClass
{
[ItemMaxLength(10)]
public IDictionary<string, string> Names { get; set; }
}
public class InvalidModelClass
{
[ItemMaxLength(10)]
public IDictionary<string, string> NotNames { get; set; }
}
public class ItemMaxLengthAttribute : ValidationAttribute
{
private readonly int _maxLength = int.MaxValue;
public ItemMaxLengthAttribute(int maxLength)
{
_maxLength = maxLength;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var propretyInfo = validationContext.ObjectType.GetProperty(validationContext.MemberName);
if (propretyInfo.Name == "Names")
return ValidationResult.Success;
return new ValidationResult("The property isn't 'Names'");
}
}
You have to use validationContext.MemberName property.
Example:
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var userManager = validationContext.GetService<UserManager<ApplicationUser>>();
var findingTask = userManager.FindByEmailAsync((string)value);
findingTask.Wait();
var user = findingTask.Result;
return user == null
? ValidationResult.Success
: new ValidationResult("This email already in use", new string[] { validationContext.MemberName });
}
I have a situation where I want to compare to fields (example, ensuring the start time is before the end time). I'm using the System.ComponentModel.DataAnnotations attributes for my validation.
My first thought was something like this:
public enum CompareToOperation
{
EqualTo,
LessThan,
GreaterThan
}
public class CompareToAttribute : ValidationAttribute
{
CompareToOperation _Operation;
IComparable _Comparision;
public CompareToAttribute(CompareToOperation operation, Func<IComparable> comparison)
{
_Operation = operation;
_Comparision = comparison();
}
public override bool IsValid(object value)
{
if (!(value is IComparable))
return false;
switch (_Operation)
{
case CompareToOperation.EqualTo: return _Comparision.Equals(value);
case CompareToOperation.GreaterThan: return _Comparision.CompareTo(value) == 1;
case CompareToOperation.LessThan: return _Comparision.CompareTo(value) == -1;
}
return false;
}
}
public class SimpleClass
{
public DateTime Start {get;set;}
[CompareTo(CompareToOperation.GreaterThan, () => this.Start)] // error here
public DateTime End {get;set;}
}
This doesn't work however, there's a compiler error where the attribute is marked:
Expression cannot contain anonymous methods or lambda expressions
Does anyone have a solution for this? Or a different approach for validating one field compared to the value of another?
Check The AccountMOdel in the default project of MVC2, There is an attribute PropertiesMustMatchAttribute applied to the ChangePasswordModel to validate that the NewPassword and ConfirmPassword Match
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class PropertiesMustMatchAttribute : ValidationAttribute
{
private const string _defaultErrorMessage = "'{0}' and '{1}' do not match.";
private readonly object _typeId = new object();
public PropertiesMustMatchAttribute(string originalProperty, string confirmProperty)
: base(_defaultErrorMessage)
{
OriginalProperty = originalProperty;
ConfirmProperty = confirmProperty;
}
public string ConfirmProperty
{
get;
private set;
}
public string OriginalProperty
{
get;
private set;
}
public override object TypeId
{
get
{
return _typeId;
}
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
OriginalProperty, ConfirmProperty);
}
public override bool IsValid(object value)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
object originalValue = properties.Find(OriginalProperty, true /* ignoreCase */).GetValue(value);
object confirmValue = properties.Find(ConfirmProperty, true /* ignoreCase */).GetValue(value);
return Object.Equals(originalValue, confirmValue);
}
}
A very ugly way that's not nearly as flexible is to put it on the class and use reflection. I haven't tested this, so I'm not actually sure it works, but it does compile :)
public enum CompareToOperation
{
EqualTo,
LessThan,
GreaterThan
}
public class CompareToAttribute : ValidationAttribute
{
CompareToOperation _Operation;
string _ComparisionPropertyName1;
string _ComparisionPropertyName2;
public CompareToAttribute(CompareToOperation operation, string comparisonPropertyName1, string comparisonPropertyName2)
{
_Operation = operation;
_ComparisionPropertyName1 = comparisonPropertyName1;
_ComparisionPropertyName2 = comparisonPropertyName2;
}
private static IComparable GetComparablePropertyValue(object obj, string propertyName)
{
if (obj == null) return null;
var type = obj.GetType();
var propertyInfo = type.GetProperty(propertyName);
if (propertyInfo == null) return null;
return propertyInfo.GetValue(obj, null) as IComparable;
}
public override bool IsValid(object value)
{
var comp1 = GetComparablePropertyValue(value, _ComparisionPropertyName1);
var comp2 = GetComparablePropertyValue(value, _ComparisionPropertyName2);
if (comp1 == null && comp2 == null)
return true;
if (comp1 == null || comp2 == null)
return false;
var result = comp1.CompareTo(comp2);
switch (_Operation)
{
case CompareToOperation.LessThan: return result == -1;
case CompareToOperation.EqualTo: return result == 0;
case CompareToOperation.GreaterThan: return result == 1;
default: return false;
}
}
}
[CompareTo(CompareToOperation.LessThan, "Start", "End")]
public class SimpleClass
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
From the look of it, this cannot be done.
ValidationAttribute is applied on a property and as such is restricted to that property only.
I assume the question is not an abstract one and you do have a real issue that requires the presence of such a validator. Probably it's the repeat password textbox? :-)
In any case, to work around the problem you have you need to rely on the context you work in. ASP.NET Web Forms did it with the ControlToCompare and since everything is a control and we have naming containers in place it's fairly easy to figure things out based on a simple string.
In ASP.NET MVC you can in theory do the same thing, BUT! Client side will be fairly easy and natural - just use the #PropertyName and do your stuff in javascript. Serverside though you would need to access something external to your attribute class - the Request object - and that is a no no as far as I'm concerned.
All in all, there IS always a reason for things (not)happening and, in my opinion, a reason why Microsoft did not implement this kind of validator in a first place is - it is not possible without things described above.
BUT! I really hope I'm wrong. I DO need the compare validation to be easy to use...
I think you need something like this:
public class EqualsAttribute : ValidationAttribute
{
private readonly String _To;
public EqualsAttribute(String to)
{
if (String.IsNullOrEmpty(to))
{
throw new ArgumentNullException("to");
}
if (String.IsNullOrEmpty(key))
{
throw new ArgumentNullException("key");
}
_To = to;
}
protected override Boolean IsValid(Object value, ValidationContext validationContext, out ValidationResult validationResult)
{
validationResult = null;
var isValid = IsValid(value, validationContext);
if (!isValid)
{
validationResult = new ValidationResult(
FormatErrorMessage(validationContext.DisplayName),
new [] { validationContext.MemberName });
}
return isValid;
}
private Boolean IsValid(Object value, ValidationContext validationContext)
{
var propertyInfo = validationContext.ObjectType.GetProperty(_To);
if (propertyInfo == null)
{
return false;
}
var propertyValue = propertyInfo.GetValue(validationContext.ObjectInstance, null);
return Equals(value, propertyValue);
}
public override Boolean IsValid(Object value)
{
throw new NotSupportedException();
}
}