Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom validation unique property - generic classes

I'm trying to make a custom validation [IsUnique]. That check if is property value is unique and return a proper message.

This is my code, but this only work for a specified class, is possible to do a method that get the proper class by the metadata?

public class ArticleMetaData
    {
        [Required(AllowEmptyStrings = false)]
        [IsUnique("Name")]
        public String Name{ get; set; }      
    }

And my custom validation:

class IsUnique : ValidationAttribute
    {
        public IsUnique(string propertyNames)
        {
            this.PropertyNames = propertyNames;
        }

        public string PropertyNames { get; private set; }

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {

            var myproperty = validationContext.ObjectType.GetProperty(PropertyNames);
            var value = propiedad.GetValue(validationContext.ObjectInstance, null);

            IEnumerable<String> properties;

            List<string> propertiesList = new List<string>();
            propertiesList.Add(myproperty.Name);

            var dba = new myContext();

            if (dba.Articles.Any(article => article.Name == (string)value))
            {
                return new ValidationResult("The name already exist", propertiesList);
            }
            return null;
        }
    }

the idea would be to just use the annotation [isUnique] and the method take the class with annotation and search the corresponding entity.

like image 1000
grteibo Avatar asked Aug 23 '15 22:08

grteibo


2 Answers

When writing Validation Attributes, you can use ValidationContext to gain some information about validation such as Name of Property that you are validating, Type of object that you are validating and so on.

So you don't need to declare which property you want to check for uniqueness, or which entity you should check, or event you don't need to retrieve value using reflection, because the value has been passed to IsValid method.

When using DbContext, you canexecute Sql queries, so you can check for uniqueness using sql query simply. It is more simple than try to Create generic linq query on the fly.

May be this idea help you. Here is some changes in your code according to the idea:

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
    var db = new YourDBContext();

    var className = validationContext.ObjectType.Name.Split('.').Last();
    var propertyName = validationContext.MemberName;
    var parameterName = string.Format("@{0}", propertyName);

    var result = db.Database.SqlQuery<int>(
        string.Format("SELECT COUNT(*) FROM {0} WHERE {1}={2}", className, propertyName, parameterName),
        new System.Data.SqlClient.SqlParameter(parameterName, value));
    if (result.ToList()[0] > 0)
    {
        return new ValidationResult(string.Format("The '{0}' already exist", propertyName),
                    new List<string>() { propertyName });
    }

    return null;
}

To use this attribute, simply put [IsUnique] above your property.

[IsUnique]
YourProperty { get; set; }

Then run a test using such code:

var db = new YourDbContext();
db.Configuration.ValidateOnSaveEnabled = true;
db.Categories.Add(new YourEntity() { YourProperty = "DuplicateName" });
db.SaveChanges();

It is a good practice to validate only such aspect of your entity using attributes, that can be validated offline.

Validation Attributes like StringLength, RegularExpression, Required and such validations are examples of good attributes and Validation Attributes that checks for uniqness or other database related rules are examples of inappropriate attributes.

like image 99
Reza Aghaei Avatar answered Sep 17 '22 04:09

Reza Aghaei


I think the best way is to let the database do its works.

Create a constraint in the database to prevent two articles have the same name (or whatever uniqueness you need). Then, when the user create a new article or update an existing one with an existing article name, the database will throw an exception. Catch that exception and let the user knows about the issue.

like image 31
Juan M. Elosegui Avatar answered Sep 18 '22 04:09

Juan M. Elosegui