13

How do I remain DRY with asp.net mvc view models & data annotation (validation, display, and data modeling) attributes with Asp.Net MVC? I have passed model objects as well as action specific view models to views. I find both directions to have some issues with trying to remain DRY.

  • Use model objects as your view model: This works fine in simple situations and allows you to only write data annotation attributes once, on each model object. The problem arises when you have complex views that require more than one object type. The resulting view model architecture is a mishmash of using view model classes and actual model classes. Additionally, this method can expose model properties to your view that you do not intend.

  • Use a unique view model class per action: The view model class only contains view specific properties, decorated with data annotation attributes. In my experience, this method has not proved to be very DRY, as data annotation attributes tend to get duplicated across view model classes. For example, New and Edit view models share a lot, but not all, of properties and data annotations.

How do I remain DRY with asp.net mvc view models & data annotation attributes?

Jim Geurts
  • 20,189
  • 23
  • 95
  • 116
  • Unfortunately, there's no perfect solution to this that doesn't result in *some* duplication of validation metadata. At least none that I've found. – Brant Bobby Dec 13 '10 at 18:53

3 Answers3

5

A good option would be to switch from DataAnnotations to Fluent Validation. It allows you to encapsulate common validation logic in a class, which you can apply later to your Models.

From the documentation:

[Validator(typeof(PersonValidator))]
public class Person {
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public int Age { get; set; }
}

public class PersonValidator : AbstractValidator<Person> {
    public PersonValidator() {
        RuleFor(x => x.Id).NotNull();
        RuleFor(x => x.Name).Length(0, 10);
        RuleFor(x => x.Email).EmailAddress();
        RuleFor(x => x.Age).InclusiveBetween(18, 60);
    }
}
CGK
  • 2,662
  • 21
  • 24
  • 2
    That definitely looks like an attractive option, but doesn't really help with this specific question. Unless I'm missing something – Jim Geurts Dec 08 '10 at 14:12
  • The answer aims to group a set of validators in a class, which you then can apply to different ViewModels. For example, you can declare a class with the common validators for the New and and Edit ViewModels, If you choose to use ViewModels, it will be inevitable to have some duplication, but I think it's a good tradeoff, mainly by the reasons you give. You could complement the first answer with this one. – CGK Dec 09 '10 at 01:12
3

I have metadata defined in c# like this:

public class Meta
{
    public class Client
    {
        public class Name
        {
            public const bool Required = true;
            public const DataType Type = DataType.Text;
            public const int MaxLength = 30;
            public const int MinLength = 1;
            public const string Regex = @"^[\w\d\.-_]{1,30}$";
        }

        public class Email
        {
            public const bool Required = false;
            public const DataType Type = DataType.EmailAddress;
            public const int MaxLength = 256;
            public const int MinLength = 4;
            public const string Regex = @"^.+@.+$";
        }
    }
}

declaring them as constants allows you to utilize DataAnnotations on both BL entities and UI models:

[DataContract]
[Serializable]
public class ClientInfo
{
    [DataMember]
    [Required(AllowEmptyStrings = !Meta.Client.Name.Required)]
    [StringLength(Meta.Client.Name.MaxLength, MinimumLength = Meta.Client.Name.MinLength)]
    [RegularExpression(Meta.Client.Name.Regex)]
    public string Name { get; set; }

    ...
}

well, yes, you duplicate the attributes but not metadata! In addtition i have a trivial preprocessor to generate sql-scripts from a template (special processing for *.Required, etc):

create table dbo.Client( Name nvarchar({#Client.Name.MaxLength}) {#Client.Name.Required}, Email nvarchar({#Client.Email.MaxLength}) {#Client.Email.Required}, ....

On UI, you can use inheritance to not duplicate properties. For example, if you have a model with 10 properties, but need to edit only 2 of them, create EditModel and inherit ViewModel from it. The key here is to have metadata in a single storage and use it as much as possible. Hope you get the idea.

UserControl
  • 14,766
  • 20
  • 100
  • 187
0

So far, I've found that using inheritance to combine shared properties works best. I'm using a unique view class per action and am pretty happy with the solution so far. It doesn't solve 100% of the cases, but it does cover the majority and almost eliminates duplicate data contract attributes.

Jim Geurts
  • 20,189
  • 23
  • 95
  • 116