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?
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);
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With