Mapping Validation Attributes From Domain Entity to DTO
Asked Answered
R

8

34

I have a standard Domain Layer entity:

public class Product
{
    public int Id { get; set; }

    public string Name { get; set; }

    public decimal Price { get; set;}
}

which has some kind of validation attributes applied:

public class Product
{
    public int Id { get; set; }

    [NotEmpty, NotShorterThan10Characters, NotLongerThan100Characters]
    public string Name { get; set; }

    [NotLessThan0]
    public decimal Price { get; set;}
}

As you can see, I have made up these attributes completely. Which validation framework (NHibernate Validator, DataAnnotations, ValidationApplicationBlock, Castle Validator, etc) in use here is not important.

In my client layer, I also have a standard setup where I don't use the Domain entities themselves, but instead map them to ViewModels (aka DTO) which my view layer uses:

public class ProductViewModel
{
    public int Id { get; set; }

    public string Name { get; set; }

    public decimal Price { get; set;}
}

Let's then say that I want my client/view to be able to perform some basic property-level validations.

The only way I see I can do this is to repeat the validation definitions in the ViewModel object:

public class ProductViewModel
{
    public int Id { get; set; }

    // validation attributes copied from Domain entity
    [NotEmpty, NotShorterThan10Characters, NotLongerThan100Characters]
    public string Name { get; set; }

    // validation attributes copied from Domain entity
    [NotLessThan0]
    public decimal Price { get; set;}
}

This is clearly not satisfactory, as I have now repeated business logic (property-level validation) in the ViewModel (DTO) layer.

So what can be done?

Assuming that I use an automation tool like AutoMapper to map my Domain entities to my ViewModel DTOs, wouldn't it also be cool to somehow transfer the validation logic for the mapped properties to the ViewModel as well?

The questions are:

1) Is this a good idea?

2) If so, can it be done? If not, what are the alternatives, if any?

Thank you in advance for any input!

Redhanded answered 15/1, 2010 at 22:53 Comment(5)
EDIT: I suppose I should mention that I'm working with ASP.NET MVC. I was originally thinking that this may not be relevant, but then figured that there are likely other types of solutions in the WinForms/WPF/Silverlight world (like MVVM) that may not apply to the web stack.Redhanded
Why do you need a DTO at all? Why not just bind to your entity class?Separates
@Josh - In order to establish a clean level of separation, among other reasons. In any case, I think debating the DTO pattern is a separate topic.Redhanded
looks like i'm not the only person asking this question. Like minds #2182440Nope
#2547632Pooh
S
12

If you're using something supporting DataAnnotations, you should be able to use a metadata class to contain your validation attributes:

public class ProductMetadata 
{
    [NotEmpty, NotShorterThan10Characters, NotLongerThan100Characters]
    public string Name { get; set; }

    [NotLessThan0]
    public decimal Price { get; set;}
}

and add it in the MetadataTypeAttribute on both the domain entity & DTO:

[MetadataType(typeof(ProductMetadata))]
public class Product

and

[MetadataType(typeof(ProductMetadata))]
public class ProductViewModel

This won't work out of the box with all validators - you may need to extend your validation framework of choice to implement a similar approach.

Shulman answered 11/2, 2010 at 8:31 Comment(3)
Sam, thanks for the input. This seems like a good approach. I'm going to do some more research on whether this metadata approach is a good one for the Domain layer. Will report any results here.Redhanded
The problem here is that if you have a ViewModel that expresses a subset of the data in a Domain object, you'll still have to include the unneeded properties in your ViewModel anyway, or else you'll get a runtime error because some of the properties can't be mapped.Usurp
@jonathonconway: if you're applying a different set of validation rules to the Domain object & ViewModel (due to a different set of attributes), I would question the value in attempting to share 'some' of them. If your ViewModel does not very closely resemble your Domain object, IMO it's not a useful exercise to attempt to apply identical validation to both. That said, if you're writing your own validator that follows this approach, you could make it ignore properties that don't exist on the target object.Shulman
F
9

The purpose of validation is to ensure that data coming into your application meets certain criteria, with that in mind, the only place it makes sense to validate property constraints, like those you have identified here, is at the point where you accept data from an untrusted source ( i.e. the user ).

You can use something like the "money pattern" to elevate validation into your domain type system and use these domain types in the view model where it makes sense. If you have more complex validation (i.e. you are expressing business rules that require greater knowledge than that expressed in a single property), these belong in methods on the domain model that apply the changes.

In short, put data validation attributes on your view models and leave them off your domain models.

Findley answered 10/2, 2010 at 0:43 Comment(2)
What if my domain is shared between multiple client applications, each with their own domain models? Wouldn't I then need to duplicate validation logic in both those client applications?Redhanded
Assuming you mean each with their own view models. Only the application of the attributes to view models is duplicated, the validation logic / attributes etc can be shared via a common validation library. You can add business level validation to your domain objects to ensure they are valid when persisted but IMHO any more than this is chasing reuse for the sake of it.Findley
S
4

Why not use an interface to express your intent? Eg:

public interface IProductValidationAttributes {
    [NotEmpty, NotShorterThan10Characters, NotLongerThan100Characters]
    string Name { get; set; }

    [NotLessThan0]
    decimal Price { get; set;}
}
Scepter answered 16/1, 2010 at 1:52 Comment(3)
Hmm. Interesting approach. I'm assuming the IProductValidationAttributes interface would be defined in the Domain layer? And implemented by both the Product domain entity and the ProductViewModel? If so, wouldn't that defeat the purpose of a viewmodel? If it has to implement an interface in the Domain layer? If my View has to take a dependency on the Domain layer, then I might as well use the original domain entities themselves, no?Redhanded
-1, because interface dictates same data types in both ViewModel and Domain Model, but this is usually not the case. ViewModels are usually made of strings, while domain objects contain integers, decimals, booleans, etc.Mastersinger
@Martin. If I was going do this, I would have the interface in a separate project. But I wouldn't take this approach. I don't like using attributes for validation, especially in the domain (you shouldn't allow your domain objects to enter an invalid state). I think you are going to cause yourself more pain than it's worth trying to reuse this logic, why not just validate on the domain side (in the constructor/factory for invariant and in the command for everything else)? If you are just doing CRUD, I would probably use an active record pattern rather than ddd and use the objects directly.Scepter
R
4

It turns out that AutoMapper may be able to do this for us automagically, which is the best case scenario.

AutoMapper-users: Transfer validation attributes to the viewmodel?
http://groups.google.com/group/automapper-users/browse_thread/thread/efa1d551e498311c/db4e7f6c93a77302?lnk=gst&q=validation#db4e7f6c93a77302

I haven't got around to trying out the proposed solutions there, but intend to shortly.

Redhanded answered 20/2, 2010 at 11:6 Comment(2)
I cannot see how this has been implemented the post at this link is not very clear. Can anyone provide any further info on how to do thisDemocracy
The google group discussion ends saying the patch has been lost. So its not in Automapper now I believe? :(Caa
M
1

If you use hand-written domain entities, why not put your domain entities in their own assembly and use that same assembly both on the client and server. You can reuse the same validations.

Minton answered 16/2, 2010 at 15:55 Comment(2)
Domain entities are already in a separate assembly: Domain. The point of the ViewModel pattern is that your Domain layer is not consumed directly by your client views (separation of concerns).Redhanded
@Martin: do you need the separation of concerns? I think where possible, using the Domain objects instead of a ViewModel is useful. If there is a mismatch, perhaps have the ViewModel wrap or decorate the Domain object, and then delegate the validation of the ViewModel to that part that validates the Domain object, and any validation specific to the View can be done after/before.Beutler
G
1

I've been considering this as well for a while now. I totally understand Brad's reply. However, let's assume I want to use another validation framework that is suitable for annotating both domain entities and view models.

The only solution I can come up with on paper that still works with attributes would be to create another attribute that "points" to a domain entity's property that you are mirroring in your view model. Here's an example:

// In UI as a view model.
public class UserRegistration {
  [ValidationDependency<Person>(x => x.FirstName)]
  public string FirstName { get; set; }

  [ValidationDependency<Person>(x => x.LastName)]
  public string LastName { get; set; }

  [ValidationDependency<Membership>(x => x.Username)]
  public string Username { get; set; }

  [ValidationDependency<Membership>(x => x.Password)]
  public string Password { get; set; }
}

A framework like xVal could possibly be extended to handle this new attribute and run the validation attributes on the dependency class' property, but with your view model's property value. I just haven't had time to flesh this out more.

Any thoughts?

Goodkin answered 17/2, 2010 at 23:45 Comment(1)
Note, this just isn't possible due to the lack of both generics and lambdas in attribute usage.Goodkin
J
0

First of all, there is no notion of "standard" domain entity. For me, standard domain entity does not have any setters to begin with. If you take that approach, you can have more meaningful api, that actually conveys something about your domain. So, you can have application service that processes your DTO, creates commands that you can execute directly against you domain objects, like SetContactInfo, ChangePrice etc. Each one of these can raise ValidationException, which in turn you can collect in your service and present to the user. You can still leave your attributes on the properties of dto for simple attribute/property level validation. For anything else, consult your domain. And even if this is CRUD application, i would avoid exposing my domain entities to presentation layer.

Jamal answered 25/1, 2010 at 13:57 Comment(1)
@Jamal - I agree with everything you've said, and this is the pattern I am currently following. However, I still find I am duplicating validation: my command objects usually have property level validations which I need to duplicate on the DTOs. Essentially, this is the same problem as my original post, but substitute Command for Entity and DTO for ViewModel. I still need to duplicate validation metadata. Necessary evil I suppose?Redhanded
H
0

Disclaimer: I know this is an old discussion, but it was closest to what I was looking for: Keeping DRY by reusing validation attributes. I hope it is not too far from the original question.

In my situation I wanted to make error messages availible in .NET views and in other viewmodels. Our entities have little to no business logic and are mainly targeted for data storage. Instead we have a large viewmodel with validation and business logic were I want to reuse error messages. Since the users are only conserned with error messages, I find this to be relevant as that is what is important to maintain easily.

I could not find a feasible way to remove logic from the partial ViewModels, but I found a way to convey the same ErrorMessage, such that it can be maintained from a single point. Since ErrorMessages are tied to the view, it can just as well be part of the ViewModel. Consts are considered static members, so defining the error messages as public string constants, we can access them outside the class.

public class LargeViewModel
{
    public const string TopicIdsErrorMessage = "My error message";

    [Required(ErrorMessage = TopicIdsErrorMessage)]
    [MinimumCount(1, ErrorMessage = TopicIdsErrorMessage)]
    [WithValidIndex(ErrorMessage = TopicIdsErrorMessage)]
    public List<int> TopicIds { get; set; }
}

public class PartialViewModel
{
    [Required(ErrorMessage = LargeViewModel.TopicIdsErrorMessage]
    public List<int> TopicIds { get; set; }
}

In our project we were using custom html for dropdownlists, such that we could not use @Html.EditorFor helper in razor, thus we could not use unobtrusive validation. With the error message availible we could now apply the necessary attributes:

    @(Html.Kendo().DropDownList()
        .Name("TopicIds")
        .HtmlAttributes(new {
            @class = "form-control",
            data_val = "true",
            data_val_required = SupervisionViewModel.TopicIdsErrorMessage
        })
    )

Warning: You might need to recompile all related projects that rely on const values...

Hallmark answered 12/6, 2019 at 7:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.