Reusing validation attributes in custom ViewModels
Asked Answered
O

5

11

When I started using xVal for client-side validation, I was only implementing action methods which used domain model objects as a viewmodel or embedded instances of those objects in the viewmodel.

This approach works fine most of the time, but there are cases when the view needs to display and post back only a subset of the model's properties (for example when the user wants to update his password, but not the rest of his profile data).

One (ugly) workaround is to have a hidden input field on the form for each property that is not otherwise present on the form.

Apparently the best practice here is to create a custom viewmodel which only contains properties relevant to the view and populate the viewmodel via Automapper. It's much cleaner since I am only transferring the data relevant to the view, but it's far from perfect since I have to repeat the same validation attributes that are already present on the domain model object.

Ideally I'd like to specify the Domain Model object as a meta class via a MetaData attribute (this is also often referred to as "buddy class"), but that doesn't work since xVal throws when the metadata class has properties that are not present on the viewmodel.

Is there any elegant workaround to this? I've been considering hacking the xVal sourcecode, but perhaps there is some other way I have overlooked so far.

Thanks,

Adrian

Edit: With the arrival of ASP.NET MVC 2, this is not only a problem related to validation attributes anymore, but it also applies to editor and display attributes.

Osullivan answered 13/1, 2010 at 20:19 Comment(5)
I too am curious about this -- I ended up just having the validation on the custom viewmodel and moving it out of my domain model.Twi
So you are repeating the validation attributes over and over again?Osullivan
I end up not having to, no. Usually I don't have multiple pages (and therefore viewmodels) that can set the same value (say firstname for a person). Rare cases I have to, yes.Twi
Unfortuantely that's not the case in my project. For instance I have a business entity that is being altered by 6 different forms.Osullivan
Ctrl + F '16, nothing. Any update on an answer to this question in 2016?Quake
S
7

This is the quintessential reason why your input screens should not be tightly coupled to your model. This question actually pops up here on the MVC tag about 3-4 times a month. I'd dupe if I could find the previous question and some of the comment discussion here is interesting. ;)

The issue your having is you're trying to force two different validation contexts of a model into a single model which fails under a large amount of scenarios. The best example is signing up a new user and then having an admin edit a user field later. You need to validate a password on a user object during registration but you won't show the password field to the admin editing the user details.

The choices for getting around these are all sub-optimal. I've worked on this problem for 3 projects now and implementing the following solutions has never been clean and usually frustrating. I'm going to try and be practical and forget all the DDD/db/model/hotnessofthemonth discussions everybody else is having.

1) Multiple View Models Having viewmodels that are almost the same violates the DRY principal but I feel the costs of this approach are really low. Usually violating DRY amps up maintenance costs but IMHO the costs for this are the lowest and don't amount to much. Hypothetically speaking you don't change how max number characters the LastName field can have very often.

2) Dynamic Metadata There are hooks in MVC 2 for providing your own metadata for a model. With this approach you could have whatever your using to provide metadata exclude certain fields based on the current HTTPRequest and therefore Action and Controller. I've used this technique to build a database driven permissions system which goes to the DB and tells the a subclass of the DataAnnotationsMetadataProvider to exclude properties based values stored in the database.

This technique is working great atm but the only problem is validating with UpdateModel(). To solve this problem we created a SmartUpdateModel() method which also goes to the database and automatically generates the exclude string[] array so that any non-permissisable fields aren't validated. We of course cached this for performance reasons so its not bad.

Just want to reiterate that we used [ValidationAttributes] on our models and then superceeded them with new rules on runtime. The end result was that the [Required] User.LastName field wasn't validated if the user didn't have permission to access it.

3) Crazy Interface Dynamic Proxy Thing The last technique I tried to was to use interfaces for ViewModels. The end result was I had a User object that inherited from interfaces like IAdminEdit and IUserRegistration. IAdminEdit and IUserRegistration would both contain DataAnnotation attributes that performed all the context specific validation like a Password property with the interfaces.

This required some hackery and was more an academic exercise than anything else. The problem with 2 and 3 is that UpdateModel and the DataAnnotationsAttribute provider needed to be customized to be made aware of this technique.

My biggest stumbling block was I didn't ever want to send the whole user object to the view so I ended up using dynamic proxies to create runtime instances of IAdminEdit

Now I understand this is a very xVal specific question but all of the roads to dynamic validation like this lead to customization of the internal MVC Metadata providers. Since all the metadata stuff is new nothing is that clean or simple to do at this point. The work you'd have to do to customize MVC's validation behavior isn't hard but requires some in depth knowledge of how all of the internals work.

Selia answered 1/11, 2010 at 17:39 Comment(1)
Thanks for your comprehensive reply! Since I wrote this OP early this year, I've tried variants of the 1) and 3) approaches you mentioned. I agree that both were less than ideal for the reasons you have already described. Regarding approach 2: Could you point me to any blog article or similar resource that describes how this would be done? It doesn't have to be related to xVal, since I am using the regular MVC 2 model binder and metadata.Osullivan
G
4

We moved our validation attributes to the ViewModel layer. In our case, this provided a cleaner separation of concerns anyway, as we were then able to design our domain model such that it couldn't get into an invalid state in the first place. For example, Date might be required on a BillingTransaction object. So we don't want to make it Nullable. But on our ViewModel, we might need to expose Nullable such that we can catch the situation where the user didn't enter a value.

In other cases, you might have validation that is specific per page/form, and you'll want to validate based on the command the user is trying to perform, rather than set a bunch of stuff and ask the domain model, "are you valid for trying to do XYZ", where in doing "ABC" those values are valid.

Guilbert answered 17/1, 2010 at 19:45 Comment(6)
So you are repeating validation attributes for views that handle changes to the the same business entities?Osullivan
Nope, we don't do validation attributes on business entities. Business entities are not allowed to get into an invalid state, so there's no need to put validation attributes on them.Guilbert
Validation is a domain concern that changes with the domain, not with the UI. Putting this domain-specific logic in the UI obliterates that separation. At the most, ViewModels should validate "simple requirements" that are domain-agnostic and do not change with the domain. For example, required fields and email formatting. All other validation should be accepted (in most cases) as strictly server-side in the domain, without client-side autogenerated support.Iata
@gWiz: I think he meant that domain objects enforce their validity through either compile-time type safety or real-time input validation, as opposed to accepting invalid input and providing a way to get at the "errors". I tend to agree with this approach. There's a subtle difference between input validation and domain validation that's often forgotten in MVC.Elka
Whether you call it input validation or domain validation, the point is to prevent invalid input from being saved into the domain. My point is that you want to keep complex logic that is specific to the domain but generic across UI's, in the domain. I understand the goal of not allowing domain objects to ever get into an invalid state. This is supported out-of-box in ASP.NET MVC via DeafultModelBinder.SetProperty, which will catch exceptions thrown while attempting to set a property in the ModelState. Not sure how an extra view model layer would be any better.Iata
I tend to use methods to update members, rather than putting a lot of logic in property setters. That tends to force order of operations (StartDate needs to be less than EndDate, but what if I set EndDate first?) that becomes more and more opaque. Validation tends to center around specific commands, rather than entities. A state valid in one context may be invalid in the next.Guilbert
I
3

If ViewModels are hypothetically being forced upon you, then I recommend that they only enforce domain-agnostic requirements. This includes things like "username is required" and "email is formatted properly".

If you duplicate validation from the domain models in the view models, then you have tightly coupled the domain to the UI. When the domain validation changes ("can only apply 2 coupon per week" becomes "can only apply 1 coupon per week"), the UI must be updated. Generally speaking, this would be awful, and detrimental to agility.

If you move the validation from the domain models to the UI, you've essentially gutted your domain and placed the responsibility of validation on the UI. A second UI would have to duplicate all the validation, and you have coupled two separate UI's together. Now if the customer wants a special interface to administrate the inventory from their iPhone, the iPhone project needs to replicate all the validation that is also found in the website UI. This would be even more awful than validation duplication described above.

Unless you can predict the future and can rule out these possibilities, only validate domain-agnostic requirements.

Iata answered 23/1, 2010 at 0:36 Comment(2)
You are describing exactly the problem that led me to writing this OP. But what solution do you propose? What exactly do you mean by "only validate domain-agnostic requirements."? And what do you propose with regards to editor and display template metadata?Osullivan
Domain-agnostic requirements are things like phone number format, email format, distance (positive number), age (positive integer), etc. These things won't change with the business--they're conceptual constants external to the system. If the view models are screen-specific, screen-specific metadata can be added directly to them. Otherwise, use the data dictionary. (c.f. bit.ly/bvL5KM) I've actually been away from ASP.NET MVC for a while but when I was using version 1, I found the strong-typing ViewData extensions from MvcContrib to be indispensable.Iata
I
2

I don't know how this will play for client-side validation, but if partial validation is your issue you can modify the DataAnnotationsValidationRunner discussed here to take in an IEnumerable<string> list of property names, as follows:

public static class DataAnnotationsValidationRunner
{
     public static IEnumerable<ErrorInfo> GetErrors(object instance, IEnumerable<string> fieldsToValidate)
     {
           return from prop in TypeDescriptor.GetProperties(instance).Cast<PropertyDescriptor>().Where(p => fieldsToValidate.Contains(p.Name))
                  from attribute in prop.Attributes.OfType<ValidationAttribute>()
                  where !attribute.IsValid(prop.GetValue(instance))
                  select new ErrorInfo(prop.Name, attribute.FormatErrorMessage(string.Empty), instance);
     }
}
Impossibility answered 18/2, 2010 at 4:22 Comment(2)
While a similar fix could be implemented on the client side by branching xVal, I wouldn't do it this way since it relies on magic strings and it's all too easy to forget adding a string here after adding a new property to the model.Osullivan
I agree it's not a perfect solution, but we're managing the magic string issue through constant collections of field names, named after the step in our workflows they refer to. That way when the controller invokes the class that performs the validation during the post, it passes in the collection of fields you know you need validated for that view.Impossibility
I
0

I'm gonna risk the downvotes and state that there is no benefit to ViewModels (in ASP.NET MVC), especially considering the overhead of creating and maintaining them. If the idea is to decouple from the domain, that is indefensible. A UI decoupled from a domain is not a UI for that domain. The UI must depend on the domain, so you're either going to have your Views/Actions coupled to the domain model, or your ViewModel management logic coupled to the domain model. The architecture argument is thus moot.

If the idea is to prevent users from hacking malicious HTTP POSTs that take advantage of ASP.NET MVC's model binding to mutate fields they shouldn't be allowed to change, then A) the domain should enforce this requirement, and B) the actions should provide whitelists of updateable properties to the model binder.

Unless you're domain is exposing something crazy like a live, in-memory object graph instead of entity copies, ViewModels are wasted effort. So to answer your question, keep domain validation in the domain model.

Iata answered 23/1, 2010 at 0:29 Comment(7)
The UI depends on the domain. It doesn't have to depend directly on the domain. Pretty much any "report" is going to require a DTO; likewise, most forms may not map perfectly to a single domain object and therefore require a specialized UI instance. The logic for DTO and ViewModel conversion is still part of the domain model. At no point have you broken any abstractions or violated any invariants. WPF even crystallizes this principle as the MVVM (Model-View-ViewModel).Elka
I would argue that reporting is a separate domain from the business domain. A single UI can unify both domains (depend on both). And I have no qualms with composing multiple domain objects into a single class for the purpose of strongly-typed views. Putting ViewModels in the domain is a blatant violation of separation of concerns. Now you can't change your UI without changing your domain. And it's arguable that ViewModels can be reused between different apps/UIs that support different usage scenarios.Iata
Btw as I understand it, in WPF-specific parlance, a ViewModel is a model of the view. It's an abstraction of the characteristics of the view. It is therefore inherently defined in the UI. The point of such a class is to allow a view to bind to properties on the model that are not directly-mappable to a simple UI element (e.g. drop-down lists). But according to the pattern, the ViewModel actually composes the domain Model and performs binding transformations between the UI and the Model.Iata
gWiz: I fully agree that validation rules should ideally reside on the model and shoiuld not be duplicated in the ViewModel. However, simply not true that you don't need custom viewmodels at all. There are many instance in which you absolultely have to use a custom viewmodel if you want to keep your view type-safe. For sample ASP.NET MVC applications like NerdDinner this might not be the case, but it happens quite often in bigger projects with more complex user interface.Osullivan
I agree with @Adrian. I'm still pretty new to ASP.NET MVC, but I have already come across examples that required a ViewModel. For one: my User contains Username and HashedPassword, but my "reset password" view needs Password and PasswordConfirmation. There is no logical mapping between the view and the model here. I use aViewModel to validate that the two passwords match and follow some other rules for length, etc., then a single password gets passed to the repository, where it is hashed and persisted in a database. How would this be done w/o a ViewModel?Luge
Thansk for the comment, that's an interesting point. I have just used the ViewData dictionary and did validation in an ActionFilter. Confirmation of this sort was the only cases where I had UI-level validation in my previous project. Since these validations are unique to action methods, reusability of ViewModel approach was overkill IMHO. I'll explore the use of ViewModels for confirmation. I was thinking login validation might leverage a ViewModel as well, but that seems like shoe-horning something into the ASP.NET MVC validation model.Iata
If you are using ViewData then you can easily get away without custom viewmodels. But a proper viewmodel together with the strongly typed input helpers in ASP.NET MVC 2 is much cleaner and safer. Add compile-time view validation to this and you have much more robust views. It sure beats the ViewData dictionary.Osullivan

© 2022 - 2024 — McMap. All rights reserved.