9

DataAnnotations does not work with buddy class. The following code always validate true. Why ?

var isValid = Validator.TryValidateObject(new Customer(), Context, results, true);

and here is the buddy class.

public partial class Customer 
{ 
    public string Name { get; set; } 
    public int Age { get; set; } 
}

[MetadataType(typeof(CustomerMetaData))]
public partial class Customer 
{ 
    public class CustomerMetaData 
    { 
        [Required(ErrorMessage = "You must supply a name for a customer.")]        
        public string Name { get; set; } 
    } 
}

Here is another thread with same question., but no answer. link text

Community
  • 1
  • 1
ashraf
  • 1,319
  • 2
  • 16
  • 24
  • I'm not shure about this, but I think you shouldn't nest the classes. Also I think this could make CustomMetaData-class inaccessible without the public-keyword. Try moving CustomerMetaData out of the Customer-class and making it public. – Alxandr Mar 11 '10 at 01:56
  • It won't work even if I moving CustomerMetaData out of Customer class and declare as public. I have concern that does TryValidateObject support buddyclass (MetadataType)? – ashraf Mar 11 '10 at 02:10

4 Answers4

30

I found the answer here: http://forums.silverlight.net/forums/p/149264/377212.aspx

MVC recognizes the MetaDataType attribute, but other projects do not. Before validating, you need to manually register the metadata class:

TypeDescriptor.AddProviderTransparent(
            new AssociatedMetadataTypeTypeDescriptionProvider(typeof(Customer), typeof(CustomerMetadata)), typeof(Customer));

var isValid = Validator.TryValidateObject(new Customer(), context, results, true);
Jeremy Gruenwald
  • 1,947
  • 1
  • 19
  • 18
  • 1
    Thanks. If I choose data annotation as my Validation framework then Validator.TryValidaeObject should validate from anywhere. – ashraf Mar 18 '10 at 15:53
  • No constructor overload for AssociatedMetadataTypeTypeDescriptionProvider class takes three arguments. I see this issue with the code snippet. – RBT May 21 '16 at 23:16
  • The mistake I was making was in the use of `TypeDescriptor.AddProviderTransparent` call. AddProviderTransparent has two overloads. They differ in their second parameter. Initially I was trying to use the one with object parameter. It didn't work. When I replaced it with second overload having Type parameter then it worked. I wonder why first overload is not working: `public static void AddProviderTransparent(TypeDescriptionProvider provider, object instance); public static void AddProviderTransparent(TypeDescriptionProvider provider, Type type);` – RBT May 22 '16 at 00:15
6

After some research I couldn't find any reason why TryValidateObject always return true if I use MetadataType (buddy class). But it works with the following code (xVal).

    public static IEnumerable<ErrorInfo> GetErrors(object instance, string name)
    {
        var metadataAttrib = instance.GetType()
                .GetCustomAttributes(typeof(MetadataTypeAttribute), true)
                .OfType<MetadataTypeAttribute>().FirstOrDefault();
        var buddyClassOrModelClass = metadataAttrib != null
                ? metadataAttrib.MetadataClassType
                : instance.GetType();
        var buddyClassProperties = TypeDescriptor.GetProperties(buddyClassOrModelClass)
            .Cast<PropertyDescriptor>();
        var modelClassProperties = TypeDescriptor.GetProperties(instance.GetType())
            .Cast<PropertyDescriptor>();

        var list = from buddyProp in buddyClassProperties
                   join modelProp in modelClassProperties on
                            buddyProp.Name equals modelProp.Name
                   from attribute in buddyProp.Attributes.OfType<ValidationAttribute>()
                   where !attribute.IsValid(modelProp.GetValue(instance))
                   select new ErrorInfo(
                       buddyProp.Name,
                       attribute.FormatErrorMessage(modelProp.Name),
                       instance);

        if (name != null)
            list = list.Where(x => x.PropertyName == name);

        return list;
    }
ashraf
  • 1,319
  • 2
  • 16
  • 24
1

Although I did not test your code in .NET 4.0, in .NET 3.5 / Silverlight 3, your metadata class should look like this:

[MetadataType(typeof(Customer.CustomerMetaData))]
public partial class Customer 
{ 
    internal sealed class CustomerMetaData 
    {
        private CustomerMetaData()
        {
        }

        [Required(ErrorMessage = "You must supply a name for a customer.")]        
        public string Name; 
    } 
}
Sergiu Damian
  • 1,420
  • 8
  • 10
1

There is an issue where the MetadataType attribute is not being recognized by the object context. While you can manually add the type descriptor before validation: TypeDescriptor.AddProviderTransparent( new AssociatedMetadataTypeTypeDescriptionProvider(typeof(Customer), typeof(CustomerMetaData)), typeof(Customer));

a more concise way to handle it would be to update the Entity Model .tt file, to add the following to each DTO:

    Type currentType = MethodBase.GetCurrentMethod().DeclaringType;
    object[] attributes = currentType.GetCustomAttributes(typeof(MetadataTypeAttribute),false);
    if(attributes.Length > 0)
    {
        //MetadataType attribute found!
        MetadataTypeAttribute metaDataAttribute = (MetadataTypeAttribute)attributes[0];
        TypeDescriptor.AddProviderTransparent(
            new AssociatedMetadataTypeTypeDescriptionProvider(
                currentType, metaDataAttribute.MetadataClassType),currentType);
    }

This will allow you to add the attributes to the partial classes:

[MetadataType(typeof(CustomerMetaData))]
public partial class Customer
{

}

public partial class CustomerMetaData
{
    [Required]
    public string CustomerName { get; set; }
}
Gareth Suarez
  • 596
  • 1
  • 5
  • 14
  • Hello @Gareth Suarez, I think this is a great idea, can you provide more information on how to edit the Entity Model.tt file? – Gabriel Espinoza Sep 23 '14 at 03:11
  • 1
    I actually tried to do it, but I don't know where should I include your snippet. I tried including it in the constructor of each DTO but I had no luck at all. Any idea? BTW, I'm trying to set the IgnoreDataMember in some of the attributes through MetadataType, this method should work, right? – Gabriel Espinoza Sep 23 '14 at 03:26