Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Multiple Interfaces with MVC DataAnnotations and MetaDataType

I am applying validation using DataAnnotations to an MVC ViewModel which is a composite of several entity framework objects and some custom logic. The validation is already defined for the entity objects in interfaces, but how can I apply this validation to the ViewModel?

My initial idea was to combine the interfaces into one and apply the combined interface to the ViewModel, but this didn't work. Here's some sample code demonstrating what I mean:

// interfaces containing DataAnnotations implemented by entity framework classes
public interface IPerson
{
    [Required]
    [Display(Name = "First Name")]
    string FirstName { get; set; }

    [Required]
    [Display(Name = "Last Name")]
    string LastName { get; set; }

    [Required]
    int Age { get; set; }
}
public interface IAddress
{
    [Required]
    [Display(Name = "Street")]
    string Street1 { get; set; }

    [Display(Name = "")]
    string Street2 { get; set; }

    [Required]
    string City { get; set; }

    [Required]
    string State { get; set; }

    [Required]
    string Country { get; set; }
}

// partial entity framework classes to specify interfaces
public partial class Person : IPerson {}
public partial class Address : IAddress {}

// combined interface
public interface IPersonViewModel : IPerson, IAddress {}

// ViewModel flattening a Person with Address for use in View
[MetadataType(typeof(IPersonViewModel))] // <--- This does not work. 
public class PersonViewModel : IPersonViewModel
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public int Age { get; set; }

    public string Street1 { get; set; }

    public string Street2 { get; set; }

    public string City { get; set; }

    public string State { get; set; }

    public string Country { get; set; }
}

My real-world problem involves about 150 properties on the ViewModel, so it's not as trivial as the sample and retyping all the properties seems like a horrible violation of DRY.

Any ideas on how to accomplish this?

like image 273
dhochee Avatar asked Oct 19 '11 05:10

dhochee


People also ask

What is the use of DataAnnotations in MVC?

DataAnnotations is used to configure your model classes, which will highlight the most commonly needed configurations. DataAnnotations are also understood by a number of.NET applications, such as ASP.NET MVC, which allows these applications to leverage the same annotations for client-side validations.

What is the use of metadatatype in it roles?

It roles is to add attribute to a class without having to modify this one. You can add this attribute that takes a single parameter to a class that will have all the attribute. Some rules restraint the use of MetaDataType. It cannot be applied to a property and can be only be applied to a single class for each class type.

How do I provide additional information in the associated metadata class?

The example applies the RequiredAttribute attribute to a data field to show how to provide additional information in the associated metadata class. The MetadataTypeAttribute attribute enables you to associate a class with a data-model partial class. In this associated class you provide additional metadata information that is not in the data model.

How do you add metadata to a data model?

You use the MetadataTypeAttribute attribute as follows: In your application, create a file in which you create the data-model partial class that you want to modify. Create the associated metadata class. Apply the MetadataTypeAttribute attribute to the partial entity class, specifying the associated ...


1 Answers

In order for this to work you will need to manually associate the interfaces as metadata for your concrete classes.

I expected to be able to add multiple MetadataType attributes but that is not permitted.

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] // Notice AllowMultiple
public sealed class MetadataTypeAttribute : Attribute

Therefore, this gives a compilation error:

[MetadataType(typeof(IPerson))] 
[MetadataType(typeof(IAddress))] // <--- Duplicate 'MetadataType' attribute 
public class PersonViewModel : IPersonViewModel

However, it works if you only have one interface. So my solution to this was to simply associate the interfaces using a AssociatedMetadataTypeTypeDescriptionProvider and wrap that in another attribute.

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class MetadataTypeBuddyAttribute : Attribute
{
    public MetadataTypeBuddyAttribute(Type modelType, Type buddyType)
    {
        TypeDescriptor.AddProviderTransparent(
           new AssociatedMetadataTypeTypeDescriptionProvider(
               modelType,
               buddyType
           ),
           modelType);
    }
}

In my situation (MVC4) the data annotation attributes on my interfaces already worked. This is because my models directly implement the interfaces instead of having multi-level inheritance. However custom validation attributes implemented at the interface level do not work.

Only when manually associating the interfaces all the custom validations work accordingly. If I understand your case correctly this is also a solution for your problem.

[MetadataTypeBuddy(typeof(PersonViewModel), typeof(IPerson))] 
[MetadataTypeBuddy(typeof(PersonViewModel), typeof(IAddress))]
public class PersonViewModel : IPersonViewModel
like image 199
Ericvf Avatar answered Sep 30 '22 19:09

Ericvf