How can I create a generic UniqueValidationAttribute in C# and DataAnnotation?

后端 未结 4 662
感动是毒
感动是毒 2021-02-03 15:44

I\'m trying to create a UniqueAttribute using the System.ComponentModel.DataAnnotations.ValidationAttribute

I want this to be generic as in I c

相关标签:
4条回答
  • 2021-02-03 16:03

    Well, after a bit of searching, I came across: http://forums.asp.net/t/1512348.aspx and I figured it out, although it involves a fair bit of code.

    Usage:

    [Required]
    [StringLength(10)]
    [Unique(typeof(ContactsManagerDataContext),typeof(Group),"name",ErrorMessage="Group already exists")]
    public string name { get; set; }
    

    The validator code:

    public class UniqueAttribute : ValidationAttribute
    {
        public Type DataContextType { get; private set; }
        public Type EntityType { get; private set; }
        public string PropertyName { get; private set; }
    
        public UniqueAttribute(Type dataContextType, Type entityType, string propertyName)
        {
            DataContextType = dataContextType;
            EntityType = entityType;
            PropertyName = propertyName;
        }
    
        public override bool IsValid(object value)
        {
            string str = (string) value;
            if (String.IsNullOrWhiteSpace(str))
                return true;
    
            // Cleanup the string
            str = str.Trim();
    
            // Construct the data context
            ConstructorInfo constructor = DataContextType.GetConstructor(new Type[0]);
            DataContext dataContext = (DataContext)constructor.Invoke(new object[0]);
    
            // Get the table
            ITable table = dataContext.GetTable(EntityType);
    
            // Get the property
            PropertyInfo propertyInfo = EntityType.GetProperty(PropertyName);
    
            // Expression: "entity"
            ParameterExpression parameter = Expression.Parameter(EntityType, "entity");
    
            // Expression: "entity.PropertyName"
            MemberExpression property = Expression.MakeMemberAccess(parameter, propertyInfo);
    
            // Expression: "value"
            object convertedValue = Convert.ChangeType(value, propertyInfo.PropertyType);
            ConstantExpression rhs = Expression.Constant(convertedValue);
    
            // Expression: "entity.PropertyName == value"
            BinaryExpression equal = Expression.Equal(property, rhs);
    
            // Expression: "entity => entity.PropertyName == value"
            LambdaExpression lambda = Expression.Lambda(equal, parameter);
    
            // Instantiate the count method with the right TSource (our entity type)
            MethodInfo countMethod = QueryableCountMethod.MakeGenericMethod(EntityType);
    
            // Execute Count() and say "you're valid if you have none matching"
            int count = (int)countMethod.Invoke(null, new object[] { table, lambda });
            return count == 0;
        }
    
        // Gets Queryable.Count<TSource>(IQueryable<TSource>, Expression<Func<TSource, bool>>)
        private static MethodInfo QueryableCountMethod = typeof(Queryable).GetMethods().First(m => m.Name == "Count" && m.GetParameters().Length == 2);
    }
    

    I don't mind it being ugly since I will package it in a DLL and reuse it, much better than implementing multiple UniqueAttribute per table/field.

    0 讨论(0)
  • 2021-02-03 16:06

    as @LBushkin mentioned, Attributes need compile time constants.

    I would change your class from:

    public class UniqueAttribute : ValidationAttribute
    

    to:

    public class UniqueAttribute<T> : ValidationAttribute  
    where T : DataContext{
    
        protected T Context { get; private set; }
    
      ...    
    
        }
    

    and use it as:

    [Required]
    [StringLength(10)]
    [Unique<DataContext>("Groups","name")]
    public string name { get; set; }
    

    This will help you inject a DataContext object, if needed, instead of creating an instance everytime

    HTH

    Edit: since an attribute cannot take a generic parameter, this could be another potential code:

    public class UniqueAttribute : ValidationAttribute{
    
        public UniqueAttribute(Type dataContext, ...){
            if(dataContext.IsSubClassOf(typeof(DataContext))){
                var objDataContext = Activator.CreateInstance(dataContext);
            }
        }
    
    }
    

    and use it as:

    [Required]
    [StringLength(10)]
    [Unique(typeof(DataContext), "Groups","name")]
    public string name { get; set; }
    

    HTH this time :)

    0 讨论(0)
  • 2021-02-03 16:07

    One problem I see already is that you can't instantiate types as parameters of attributes.

    Attributes require that all arguments be compile-time constants. So the usage:

    [Unique(new DataContext(),"Groups","name")]
    

    won't compile. You may be able to omitt new DataContext() - but then I suspect your validation logic won't have information about the entity types to query.

    0 讨论(0)
  • 2021-02-03 16:10

    I edited this one..and it works perfectly with DI..:D

    public class UniqueAttribute : ValidationAttribute
    {
        public UniqueAttribute(Type dataContextType, Type entityType, string propertyName)
        {
            DataContextType = dataContextType;
            EntityType = entityType;
            PropertyName = propertyName;
        }
    
    
        public Type DataContextType { get; private set; }
    
    
        public Type EntityType { get; private set; }
    
    
        public string PropertyName { get; private set; }
    
    
        public override bool IsValid(object value)
        {
            // Construct the data context
            //ConstructorInfo constructor = DataContextType.GetConstructor(new Type[0]);
            //DataContext dataContext = (DataContext)constructor.Invoke(new object[0]);
            var repository = DependencyResolver.Current.GetService(DataContextType);
            var data = repository.GetType().InvokeMember("GetAll", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public, null, repository, null);
    
            // Get the table
            //ITable table = dataContext.GetTable(EntityType);
    
    
            // Get the property
            PropertyInfo propertyInfo = EntityType.GetProperty(PropertyName);
    
    
            // Our ultimate goal is an expression of:
            //   "entity => entity.PropertyName == value"
    
    
            // Expression: "value"
            object convertedValue = Convert.ChangeType(value, propertyInfo.PropertyType);
            var rhs = Expression.Constant(convertedValue);
    
    
            // Expression: "entity"
            var parameter = Expression.Parameter(EntityType, "entity");
    
    
            // Expression: "entity.PropertyName"
            var property = Expression.MakeMemberAccess(parameter, propertyInfo);
    
    
            // Expression: "entity.PropertyName == value"
            var equal = Expression.Equal(property, rhs);
    
    
            // Expression: "entity => entity.PropertyName == value"
            var lambda = Expression.Lambda(equal, parameter).Compile();
    
            // Instantiate the count method with the right TSource (our entity type)
            MethodInfo countMethod = QueryableCountMethod.MakeGenericMethod(EntityType);
    
            // Execute Count() and say "you're valid if you have none matching"
            int count = (int)countMethod.Invoke(null, new object[] { data, lambda });
            return count == 0;
        }
    
    
        // Gets Queryable.Count<TSource>(IQueryable<TSource>, Expression<Func<TSource, bool>>)
        //private static MethodInfo QueryableCountMethod = typeof(Enumerable).GetMethods().First(m => m.Name == "Count" && m.GetParameters().Length == 2);
        private static MethodInfo QueryableCountMethod = typeof(System.Linq.Enumerable).GetMethods().Single(
            method => method.Name == "Count" && method.IsStatic && method.GetParameters().Length == 2);
    }
    
    0 讨论(0)
提交回复
热议问题