C# adding DataAnnotations to entities from the EntityFramework

孤街醉人 提交于 2019-12-07 13:33:38

问题


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;
    }
}

回答1:


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);
    }
}



回答2:


Try to validate with :

using System.ComponentModel.DataAnnotations;

Validator.TryValidateProperty(propertyValue,
                              new ValidationContext(this, null, null)
                                 { MemberName = propertyName },
                              validationResults);



回答3:


I had the same problem, and found my solution at http://blogs.msdn.com/b/davidebb/archive/2009/07/24/using-an-associated-metadata-class-outside-dynamic-data.aspx

The key was to call the static function TypeDescriptor.AddProvider()

using System.ComponentModel.DataAnnotations;

TypeDescriptor.AddProvider( new AssociatedMetadataTypeTypeDescriptionProvider(typeof(YourEntityClass)), typeof(YourEntityClass));



回答4:


Came up with this workaround because the CustomAttributes weren't being applied to the Employee class's properties. So I just got the MetaDataType class on the entity, found the corresponding property and ran the value through the ValidationAttributes.

public static class EntityHelper
{
    public static string ValidateProperty(object obj, string propertyName)
    {
        // get the MetadataType attribute on the object class
        Type metadatatype = obj.GetType().GetCustomAttributes(true).OfType<MetadataTypeAttribute>().First().MetadataClassType;
        // get the corresponding property on the MetaDataType class
        PropertyInfo property = metadatatype.GetProperty(propertyName);
        // get the value of the property on the object
        object value = obj.GetType().GetProperty(propertyName).GetValue(obj, null);
        // run the value through the ValidationAttributes on the corresponding property
        List<string> errors = (from v in property.GetCustomAttributes(true).OfType<ValidationAttribute>() where !v.IsValid(value) select v.ErrorMessage).ToList();           
        // return all the errors, or return null if there are none
        return (errors.Count > 0) ? String.Join("\r\n", errors) : null;
    }
}

[MetadataType(typeof(Employee.MetaData))]
public partial class Employee:IDataErrorInfo
{
    private sealed class MetaData
    {
        [Required(AllowEmptyStrings = false, ErrorMessage = "A name must be defined for the employee.")]
        [StringLength(50, MinimumLength = 3, ErrorMessage = "The name must  be between 3-50 characters long.")]
        public object Name { get; set; }

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

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

    public string Error { get { return String.Empty; } }
    public string this[string property] { get { return EntityHelper.ValidateProperty(this, property); } }
}


来源:https://stackoverflow.com/questions/6945625/c-sharp-adding-dataannotations-to-entities-from-the-entityframework

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!