Is it possible to reuse the DataAnnotations in ViewModel?

1k views Asked by At

In my MVC application, I defined the DataAnnotations in the domain models. Although the DataAnnotations properties as Display, etc. can be retrieved when using Domain model, they cannot be retrieved when using the same properties on ViewModel and using this ViewModel. I think it is not seem to good to define the DataAnnotations in ViewModel again. So, is it possible or which way should I follow?


Domain Model:

public class Issue
{
    [Key] 
    public int ID { get; set; }

    [Required(ErrorMessage = "Required")]
    [Display(Name = "Project Number")]
    public int ProjectID { get; set; }

    [Required(ErrorMessage = "Required")]
    [Display(Name = "Issue Definition")]
    public string Description { get; set; }

    //... removed for brevity

    //Navigation Properties:
    public virtual ICollection<FileAttachment> FileAttachments { get; set; }
}


ViewModel:

public class IssueViewModel
{
    public int ID { get; set; }

    public int ProjectID { get; set; }

    public string Description { get; set; }

    //... removed for brevity

    //Navigation Properties:
    public virtual ICollection<FileAttachment> FileAttachments { get; set; }      
}
2

There are 2 answers

5
Farhad Jabiyev On

You can create a new buddy class which holds all metadata about properties and class.

public partial class IssueMetadata
{
    [Required(ErrorMessage = "Required")]
    [Display(Name = "Project Number")]
    public int ProjectID { get; set; }

    [Required(ErrorMessage = "Required")]
    [Display(Name = "Issue Definition")]
    public string Description { get; set; }
}

Then, we must tell the MVC Framework about the buddy class through the MetadataType attribute, which takes the type of the buddy class as its argument. Buddy classes must be defined in the same namespace and must also be partial classes.

[MetadataType(typeof(IssueMetadata))]
public partial class IssueViewModel
{
      //...

      public int ProjectID { get; set; }
      public string Description { get; set; }

      //...
}

[MetadataType(typeof(IssueMetadata))]
public partial class Issue
{
      [Key] 
      public int ID { get; set; }

      public int ProjectID { get; set; }
      public string Description { get; set; }

      //... removed for brevity

      //Navigation Properties:
      public virtual ICollection<FileAttachment> FileAttachments { get; set; }
}

Additional note:
If IssueMetadata and Issue (or IssueViewModel) classes located in different assemblies, then you can associate classes with their buddy class in runtime, like that:

public class AssociatedMetadataConfig
{
    public static void RegisterMetadatas()
    {
        RegisterPairOfTypes(typeof(Issue), typeof(IssueMetadata));
        RegisterPairOfTypes(typeof(IssueViewModel), typeof(IssueMetadata));
    }

    private static void RegisterPairOfTypes(Type mainType, Type buddyType)
    {
        AssociatedMetadataTypeTypeDescriptionProvider typeDescriptionProvider 
          = new AssociatedMetadataTypeTypeDescriptionProvider(mainType, buddyType);

        TypeDescriptor.AddProviderTransparent(typeDescriptionProvider, mainType);
    }
}

And, just call this static method in global.asax:

AssociatedMetadataConfig.RegisterMetadatas();
2
ilter On

@StephenMuecke is right. DomainModel attributes and ViewModel attributes are different and you can use them seperately in your models. But I would use inheretence in this case, if I were you. You can create a Partial class for ViewModel and inherit your DomainModel from this ViewModel class.

Like:

public class IssueVM
{
    [Key] 
    public int ID { get; set; }

    [Required(ErrorMessage = "Required")]
    [Display(Name = "Project Number")]
    public int ProjectID { get; set; }

    [Required(ErrorMessage = "Required")]
    [Display(Name = "Issue Definition")]
    public string Description { get; set; }

    //... removed for brevity

    //Navigation Properties:
    public virtual ICollection<FileAttachment> FileAttachments { get; set; }
}

public class IssueDM : IssueVM
{
    // Other Fields
}

That way you have a base class of ViewModel (less fields) and a larger class with more fields for DB operations. Your ViewModel data annotation attributes are also inherited in your DomainClass that way.

I don't claim that this is the best way, but I'am using this and works fine.