When you use a metadata class in Entity Framework the registration between the entity and the metadata class is not registered for all Microsoft Visual Studio Project types (the "test project class library" for example).

I found the solution at: http://stackoverflow.com/questions/2657358/net-4-rtm-metadatatype-attribute-ignored-when-using-validator. It demonstrates registring all metadata classes in an assembly.

 

Solution

Add the following class, to the assembly that contains the Entity Framework model (*.edmx) and the metadata classes:

using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Reflection;

/// <summary>
/// Metadata classes are not always automatically registered, for example in a UnitTest classlibrary.
/// This class can be used to register all metadata classes find in this assembly.
/// 
/// Registration will only be done once.
/// </summary>
public static class MetadataTypesRegister
{
    private static bool _installed = false;
    private static readonly object InstalledLock = new object();


    /// <summary>
    /// Register all metadata classes found in this assembly.
    /// Registration will only be done once.        
    /// </summary>
    public static void InstallForAssembly()
    {
        if (_installed)
        {
            return;
        }

        lock (InstalledLock)
        {
            if (_installed)
            {
                return;
            }

            foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
            {
                foreach (MetadataTypeAttribute attrib in type.GetCustomAttributes(typeof(MetadataTypeAttribute), true))
                {
                    TypeDescriptor.AddProviderTransparent(new AssociatedMetadataTypeTypeDescriptionProvider(type, attrib.MetadataClassType), type);
                }
            }

            _installed = true;
        }
    }
}

 

In entity framework, you define metadata attributes in an internal sealed metadata "buddy" class like:

[MetadataType(typeof(CustomerMetadata))]
    public partial class Customer
    {
        internal sealed class CustomerMetadata
        {

            [Required(ErrorMessage = "Id is required")]
            public Int32 Id { get; set; }

            [Required(ErrorMessage = "Name is required")]
            public String Name { get; set; }

            [DataType(DataType.EmailAddress)]
            public String Email { get; set; }

            [RegularExpression("^[0-9]{4}[a-z|A-Z]{2}$")]
            public String Zipcode { get; set; }

        }
    }

You can unit test the metadata attributes by using the following unit test class.

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Research.Dal2;
using System.ComponentModel.DataAnnotations;

[TestClass]
public class CustomerTester
{
    private TestContext testContextInstance;

    /// <summary>
    ///Gets or sets the test context which provides
    ///information about and functionality for the current test run.
    ///</summary>
    public TestContext TestContext
    {
        get
        {
            return testContextInstance;
        }
        set
        {
            testContextInstance = value;
        }
    }

    [ClassInitialize()]
    public static void MyClassInitialize(TestContext testContext) 
    {
        MetadataTypesRegister.InstallForAssembly();
    }
    
    [TestMethod]
    public void ValidateZipcodeTest()
    {
        var customer = new Research.Dal2.Customer();
        customer.Zipcode = "this is a wrong zipcode";
        var vc = new ValidationContext(customer, null, null) { MemberName = "Zipcode" };
        var validationResults = new List<ValidationResult>();

        // Validate only the zip code.
        bool isValidZipCode = Validator.TryValidateProperty(customer.Zipcode, vc, validationResults);
        Assert.IsFalse(isValidZipCode);

        // Validate the whole Customer entity.
        bool isValidCustomer = Validator.TryValidateObject(customer, vc, validationResults, true);
        Assert.IsFalse(isValidCustomer);
    }
}

 

2 Comments

  1. When performing unit tests in a library separate from the metadata, the assembly in which the data objects and their metadatadescriptors reside need to referred to in “InstallForAssembly”, instead of Assembly.GetExecutingAssembly().

    Thanks for the article though.

    It feels a bit strange to perform this step when creating a unit test. In the real world one does not perform this step, so the unit test feels a bit detached.

    Robert Sirre
  2. I’ve been searching for a long time now…
    Your solution is great.

    I see a lot about Unit Testing but my case is very functional.
    The website I am working on offers a GUI to set up the data model as well as the possibility of loading a list of entities from an Excel file.

    As we are using EntityFramework and Metadata class for validation, your solution is evidently very useful in order to avoid copying the validation attributes.

    Thanks a lot for sharing.

    Erwann Blancart

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.