views:

403

answers:

2

Given this POCO class that was automatically generated by an EntityFramework T4 template (has not and can not be manually edited in any way):

public partial class Customer
{
    [Required]
    [StringLength(20, ErrorMessage = "Customer Number - Please enter no more than 20 characters.")]
    [DisplayName("Customer Number")]
    public virtual string CustomerNumber { get;set; }

    [Required]
    [StringLength(10, ErrorMessage = "ACNumber - Please enter no more than 10 characters.")]
    [DisplayName("ACNumber")]
    public virtual string ACNumber{ get;set; }
}

Note that "ACNumber" is a badly named database field, so the autogenerator is unable to generate the correct display name and error message which should be "Account Number".

So we manually create this buddy class to add custom attributes that could not be automatically generated:

[MetadataType(typeof(CustomerAnnotations))]
public partial class Customer { }

public class CustomerAnnotations
{
    [NumberCode] // This line does not work
    public virtual string CustomerNumber { get;set; }

    [StringLength(10, ErrorMessage = "Account Number - Please enter no more than 10 characters.")]
    [DisplayName("Account Number")]
    public virtual string ACNumber { get;set; }
}

Where [NumberCode] is a simple regex based attribute that allows only digits and hyphens:

[AttributeUsage(AttributeTargets.Property)]
public class NumberCodeAttribute: RegularExpressionAttribute
{
    private const string REGX = @"^[0-9-]+$"; 
    public NumberCodeAttribute() : base(REGX) { }
}

NOW, when I load the page, the DisplayName attribute works correctly - it shows the display name from the buddy class not the generated class.

The StringLength attribute does not work correctly - it shows the error message from the generated class ("ACNumber" instead of "Account Number").

BUT the [NumberCode] attribute in the buddy class does not even get applied to the AccountNumber property:

foreach (ValidationAttribute attrib in prop.Attributes.OfType<ValidationAttribute>())
{
    // This collection correctly contains all the [Required], [StringLength] attributes
    // BUT does not contain the [NumberCode] attribute
    ApplyValidation(generator, attrib);
}

Why does the prop.Attributes.OfType<ValidationAttribute>() collection not contain the [NumberCode] attribute? NumberCode inherits RegularExpressionAttribute which inherits ValidationAttribute so it should be there.

If I manually move the [NumberCode] attribute to the autogenerated class, then it is included in the prop.Attributes.OfType<ValidationAttribute>() collection.

So what I don't understand is why this particular attribute does not work in when in the buddy class, when other attributes in the buddy class do work. And why this attribute works in the autogenerated class, but not in the buddy. Any ideas?

Also why does DisplayName get overriden by the buddy, when StringLength does not?

+1  A: 

Hi JK

I recreated your code using VS2008 and MVC2 and it worked fine for me. Would you like me to send you the solution?

Greetz Dick.

Dick Klaver
Can you post it here? I'm using VS2010 and .NET 4 btw.
JK
+1  A: 

I noticed that your NumberCodeAttribute doesn't specify AllowMultiple=True in the AttributeUsage attribute. The default for that parameter (if it isn't specified) is false. Try adding that on and it should appear.

Josh E
Your answer sounded promising at first, but then I realized that I only use the [NumberCode] attribute once, so the problem can't be caused by AllowMultiple=false. I set it to be true and it made no difference.
JK
The fact that [DisplayName] can be overidden in the buddy class, but [StringLength] can not could however be caused by AllowMultiple=false. I just checked with reflector and [DisplayName] does not specify AllowMultiple, but [StringLength] says AllowMultiple=false. perhaps the default value for AllowMultiple is == true?
JK
hmmm... according to MSDN, AllowMultiple defaults to false. Interesting results that reflector put up for you. Is it possible to get EF to not apply the validation attributes to the generated POCOs, instead leaving only your buddy classes and their attributes? That would be a cleaner solution in general I think.
Josh E
Yes, I can do it on the EF layer by changing the TT template to say "if field name == ACNumber then apply [NumberCode] attribute". Its not clean, but at least it is automatically applied every time the POCOs are generated.
JK
That might work, I was more thinking along the lines of removing any validation attributes from your EF-generated classes entirely, leaving all validation for the buddy classes. It gives you a lot more flexibility I think in your domain model; you can apply the buddy classes to viewmodels and DTO's that share the same or similar structure as your POCO's
Josh E