1

I have DTOs that are mapped to ViewModels. To avoid having to manage validation attributes (and other attributes), I wanted to write the validation attributes for all the properties on a single class and reuse it on my ViewModels. However, when I try to use the Metadata on a ViewModel that does not have all the properties of the DTO (all of them really...), it gives me an System.InvalidOperationException exception.

Exception:

Le type de métadonnées associé pour le type 'MyProject.EntityViewModel' contient les propriétés ou champs inconnus suivants : AnotherProperty. Vérifiez que les noms de ces membres correspondent aux noms des propriétés du type principal.

Google translated:

The type associated metadata for type 'MyProject.EntityViewModel' contains the following unknown properties or fields: AnotherProperty. Verify that the names of these members match the names of the properties of the main type.

Simplified example:

public class Entity {
    public string A { get; set; }
    public string B { get; set; }
    public string C { get; set; }
}

public class EntityDTO {
    public string A { get; set; }
    public string B { get; set; }
    public string C { get; set; }
}

//This class is used to add validation attributes for input-related view models
public class EntityInputValidation {
    [Required]
    public string A { get; set; }

    [Required]
    public string B { get; set; }

    //Notice that we dont have a validation for C
}

//This class is a ViewModel used to create a new Entity
[MetadataType(typeof(EntityInputValidation))]
public class EntityCreateViewModel {
    //Required because we use the InputValidation metadata
    public string A { get; set; }

    //Notice that we do not have the B property here, even if we are using the Input Validation which has a required attribute for this property. This is where the exception comes from.

    //C is only required in this View/ViewModel
    [Required]
    public string C { get; set; }
}

Because EntityViewModel does not have AnotherProperty, it will throw an exception. Is there a way to prevent this?

Pluc
  • 2,909
  • 1
  • 22
  • 36

1 Answers1

2

I would certainly reconsider having those annotations directly on your entity. As you can already see, this is going to cause you problems whenever you need to use that entity in a view which doesn't need to adhere to those validation rules. That will likely get worse in the long run if more views are added which use your entity.

Pretty much whatever solution you come up with to stop that throwing the exception is going to be a hack.

Updated per comments

I did not want to search into 20 view models whenever we have to change a validation rule... We currently have 2 websites and soon to be 3 that are part of the solution using the same DAL and business logic. Thats a lot of view models to keep updated.

That is certainly a valid concern, and this is also a valid question to be asking. The problem is more that there is no well-defined solution, at least that I've found.

Taking a look at the inheritance idea, it seems reasonable at first. However, this is only going to work if your properties fit into neat groups, which from your updated question seems may not be the case.

Let's take a simple example:

public class LoginValidation
{
    [Required]
    public string Username { get; set; }
    [Required]
    public string Password { get; set; }
}

You could then derive a view model from that:

public class ViewModelA : LoginValidation
{
    public string SomeOtherProperty { get; set; }
}

However, this comes with a problem. What if you want to inherit another set of validation properties? You can't, as we're restricted to inheriting from one class. We also cannot inherit data annotations from interfaces:

The product team does not want to implement this feature, for two main reasons:

  1. Consistency with DataAnnotations.Validator
  2. Consistency with validation behavior in ASP.Net MVC

    tricky scenario: a class implements two interfaces that have the same property, but with conflicting attributes on them. Which attribute would take precedence?

(Source: http://social.msdn.microsoft.com/Forums/en-US/1748587a-f13c-4dd7-9fec-c8d57014632c/code-first-dataannotations-in-interfaces?forum=adonetefx)

So what if you need LoginValidation and some dates validation for a specific view? You'd have to create an inheritance chain from both in an intermediary class, just to be able to inherit from that for your view model:

public class LoginAndDateValidation : LoginValidation
{
    [Required]
    public DateTime StartDate { get; set; }
    [Required]
    public DateTime EndDate { get; set; }
}

public class ViewModelA : LoginAndDateValidation
{
    public string SomeOtherProperty { get; set; }
}

Do you see where this is going? This would turn into a complete mess. So, as I said earlier, this will only work if your properties fit into, and are used in, well-defined groups, but it doesn't seem that is the case in your scenario.

To finish up, let me just link to an answer Mystere Man posted a few years back that I've always liked: https://stackoverflow.com/a/8075115/729541

Community
  • 1
  • 1
John H
  • 14,422
  • 4
  • 41
  • 74
  • A validation rule applied on a property should be applied everywhere. If you validate that a string is between 5 and 10 characters, and your database field has 10 bytes, why would you ever allow someone to write over 10 bytes? Besides, like I said in the question, I know it should not be on the DTO. This was just a simplified example. So if I do happen to write a special view dans does not want the validation, I will not use the MetadataType attribute on it. – Pluc Nov 07 '13 at 14:54
  • @Pluc `If you validate that a string is between 5 and 10 characters, and your database field has 10 bytes, why would you ever allow someone to write over 10 bytes?` That might be true for a simple string, but that's not going to be the case for every property on your model. I've seen many questions on here with people wanting `RequiredIf` functionality. ([Here](http://stackoverflow.com/questions/7390902/requiredif-conditional-validation-attribute) is one such example.) – John H Nov 07 '13 at 14:57
  • Updated the question to show a more complete version of what I am trying to acheive. – Pluc Nov 07 '13 at 15:00
  • Regarding the RequiredIf, this would be a View-specific validation. My "EntityInputValidation" wouldn't have RequiredIf. It would be on my ViewModel. Updating the question. – Pluc Nov 07 '13 at 15:03
  • @Pluc Alright, in that sense then, if you're happy with having view-specific validation in one place, and common validation in another, isn't your problem really because you're including a property on your metadata class that isn't common to your views? – John H Nov 07 '13 at 15:09
  • 1
    @Pluc please update your question with an _actual_ entity and DTO for clarity. If `AnotherProperty` is entity-only, you cannot use the metadata that annotates `AnotherProperty` as required and use it on your DTO, as your DTO doesn't contain that property. You can however try to create a base metadata class with shared properties, and extend that with entity- or DTO specific properties and apply the extended class in the `MetaDataType` attribute. – CodeCaster Nov 07 '13 at 15:11
  • @JohnH Yes my problem is that I want to define all the properties validation in a single metadata class but the viewmodels will not necessarly have all the properties. – Pluc Nov 07 '13 at 15:15
  • @CodeCaster Sorry? AnotherProperty is in the DTO. What do you mean? Can you write that as an answer? – Pluc Nov 07 '13 at 15:17
  • @Pluc Here's the problem though: metadata is a contract. It's specifically describing data rules for a concrete set of data. CodeCaster's approach will certainly work, but if your motivation for wanting to group validation rules together was because of concerns with DRY, you're still going to be repeating yourself with an inheritance hierarchy. – John H Nov 07 '13 at 15:19
  • @JohnH My concerns were indeed with DRY concept. I did not want to search into 20 view models whenever we have to change a validation rule... We currently have 2 websites and soon to be 3 that are part of the solution using the same DAL and business logic. Thats a lot of view models to keep updated. Is there another solution you could suggest if it's not possible with MetadataType attribute? – Pluc Nov 07 '13 at 15:27
  • @JohnH I also really like the answer from Mystere Man. Thank you for your complete answer and reference. – Pluc Nov 07 '13 at 16:43