Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# adding DataAnnotations to entities from the EntityFramework

I'm using the ADO.Net Entity Framework. To handle input validation I'm trying to use DataAnnotations, I looked around on StavkOverflow and Google, and everywhere I found almost the same example of using MetadataType. However, I've been trying for hours and I cannot get it to work.. For some reason, the CustomAttributes from the EmployeeMetaData class are not being applied to the respective field/properties on the Employee class. Does anyone have any idea why this might be happening? And yes, I'm sure the property types and names match perfectly.

Any help is appreciated, I've been stuck on this for hours. Thanks in advance.

EntityExtentions.cs

[MetadataType(typeof(EmployeeMetaData))]
public partial class Employee:IDataErrorInfo
{
    public string Error { get { return String.Empty; } }
    public string this[string property]
    {
        get
        {
            return EntityHelper.ValidateProperty(this, property);
        }
    }
}

public class EmployeeMetaData
{
    [Required(AllowEmptyStrings=false, ErrorMessage = "A name must be defined for the employee.")]
    [StringLength(50, ErrorMessage = "The name must  be less than 50 characters long.")]
    public string Name { get; set; }

    [Required(ErrorMessage = "A username must be defined for the employee.")]
    [StringLength(20, MinimumLength = 3, ErrorMessage = "The username must be between 3-20 characters long.")]
    public string Username { get; set; }

    [Required(ErrorMessage = "A password must be defined for the employee.")]
    [StringLength(20, MinimumLength = 3, ErrorMessage = "The password must be between 3-20 characters long.")]
    public string Password { get; set; }
}

EntityHelper.cs

public static class EntityHelper
{
    public static string ValidateProperty(object obj, string propertyName)
    {
        PropertyInfo property = obj.GetType().GetProperty(propertyName);
        object value = property.GetValue(obj, null);
        List<string> errors = (from v in property.GetCustomAttributes(true).OfType<ValidationAttribute>() where !v.IsValid(value) select v.ErrorMessage).ToList();

        // I was trying to locate the source of the error
        // when I print out the number of CustomAttributes on the property it only shows
        // two, both of which were defined by the EF Model generator, and not the ones
        // I defined in the EmployeeMetaData class
        // (obj as Employee).Username = String.Join(", ", property.GetCustomAttributes(true));

        return (errors.Count > 0) ? String.Join("\r\n", errors) : null;
    }
}
like image 806
Saad Imran. Avatar asked May 15 '26 06:05

Saad Imran.


2 Answers

I used this one (URLs points to helpful articles where I took some ideas):

// http://www.clariusconsulting.net/blogs/kzu/archive/2010/04/15/234739.aspx
/// <summary>
/// Validator provides helper methods to execute Data annotations validations
/// </summary>
public static class DataValidator
{
    /// <summary>
    /// Checks if whole entity is valid
    /// </summary>
    /// <param name="entity">Validated entity.</param>
    /// <returns>Returns true if entity is valid.</returns>
    public static bool IsValid(object entity)
    {
        AssociateMetadataType(entity);

        var context = new ValidationContext(entity, null, null);
        return Validator.TryValidateObject(entity, context, null, true);
    }

    /// <summary>
    /// Validate whole entity
    /// </summary>
    /// <param name="entity">Validated entity.</param>
    /// <exception cref="ValidationException">The entity is not valid.</exception>
    public static void Validate(object entity)
    {
        AssociateMetadataType(entity);

        var context = new ValidationContext(entity, null, null);
        Validator.ValidateObject(entity, context, true);
    }

    /// <summary>
    /// Validate single property of the entity.
    /// </summary>
    /// <typeparam name="TEntity">Type of entity which contains validated property.</typeparam>
    /// <typeparam name="TProperty">Type of validated property.</typeparam>
    /// <param name="entity">Entity which contains validated property.</param>
    /// <param name="selector">Selector for property being validated.</param>
    /// <exception cref="ValidationException">The value of the property is not valid.</exception>
    public static void ValidateProperty<TEntity, TProperty>(TEntity entity, Expression<Func<TEntity, TProperty>> selector) where TEntity : class
    {
        if (selector.Body.NodeType != ExpressionType.MemberAccess)
        {
            throw new InvalidOperationException("Only member access selector is allowed in property validation");
        }

        AssociateMetadataType(entity);

        TProperty value = selector.Compile().Invoke(entity);
        string memberName = ((selector.Body as MemberExpression).Member as PropertyInfo).Name;

        var context = new ValidationContext(entity, null, null);
        context.MemberName = memberName;
        Validator.ValidateProperty(value, context);
    }

    /// <summary>
    /// Validate single property of the entity.
    /// </summary>
    /// <typeparam name="TEntity">Type of entity which contains validated property.</typeparam>
    /// <param name="entity">Entity which contains validated property.</param>
    /// <param name="memberName">Name of the property being validated.</param>
    /// <exception cref="InvalidOperationException">The entity does not contain property with provided name.</exception>
    /// <exception cref="ValidationException">The value of the property is not valid.</exception>
    public static void ValidateProperty<TEntity>(TEntity entity, string memberName) where TEntity : class
    {
        Type entityType = entity.GetType();
        PropertyInfo property = entityType.GetProperty(memberName);

        if (property == null)
        {
            throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, 
                "Entity does not contain property with the name {0}", memberName));
        }

        AssociateMetadataType(entity);

        var value = property.GetValue(entity, null);

        var context = new ValidationContext(entity, null, null);
        context.MemberName = memberName;
        Validator.ValidateProperty(value, context);
    }

    // http://buildstarted.com/2010/09/16/metadatatypeattribute-with-dataannotations-and-unit-testing/
    // Data Annotations defined by MetadataTypeAttribute are not included automatically. These definitions have to be injected.
    private static void AssociateMetadataType(object entity)
    {
        var entityType = entity.GetType();

        foreach(var attribute in entityType.GetCustomAttributes(typeof(MetadataTypeAttribute), true).Cast<MetadataTypeAttribute>())
        {
            TypeDescriptor.AddProviderTransparent(
                new AssociatedMetadataTypeTypeDescriptionProvider(entityType, attribute.MetadataClassType), entityType);
        }
    }
}

The biggest disadvantages of this validator were:

  • It performs like a snail. It doesn't matter if you execute it on single entity but matter a lot if you want to work with hundreds, thousands or more entities.
  • It doesn't support complex types out of the box - you must create special attribute and use it in metadata to validate complex type as well
  • It was last time I used DataAnnotations for any business validation. They are mostly useful for UI validation only with few entities.

Attribute for validating complex type / nested object:

/// <summary>
/// Attribute for validation of nested complex type.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class ValidateComplexTypeAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        return DataValidator.IsValid(value);
    }
}
like image 113
Ladislav Mrnka Avatar answered May 16 '26 21:05

Ladislav Mrnka


Try to validate with :

using System.ComponentModel.DataAnnotations;

Validator.TryValidateProperty(propertyValue,
                              new ValidationContext(this, null, null)
                                 { MemberName = propertyName },
                              validationResults);
like image 39
Philippe Lavoie Avatar answered May 16 '26 20:05

Philippe Lavoie