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;
}
}
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);
}
}
Try to validate with :
using System.ComponentModel.DataAnnotations;
Validator.TryValidateProperty(propertyValue,
new ValidationContext(this, null, null)
{ MemberName = propertyName },
validationResults);
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));
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