Update: Bart’s comment below got me thinking, so I’ve written a follow up post exploring this further and discussing the difference between binding a collection (where the validation etc will work) and working with repeating forms (where either the client or the server will have issues).
I’m a big fan of validation using Data Annotations as used in Silverlight, WPF and ASP.NET MVC (and, hopefully, eventually ASP.NET Web Forms as well). I love the elegance of placing the validation rule directly inside the business object. To me, validation is a form of business rule, and proper encapsulation means putting the rules within the business object. Of course, the fact that the tools pick up on the Data Annotations and automatically integrate the rules into the UI really helps: without that, Data Annotations would be a great idea, but Too Much Work. ASP.NET MVC can pick up Data Annotations on both the server and the client, thus leading to nicely separated, elegant code.
The only trouble is, sometimes it doesn’t work.
If you’ve never seen Data Annotation validation, here is what the attributes look like when decorating a property:
The problem lies in the way the validation has been implemented. It works fine for most circumstances, but not if you have repeating forms on the page.
The code to insert the validation error into the View is as follows (regardless of whether the error was picked up on the server or the client):
This writes out the appropriate validation for a form element with the ID Name. And therein lies the difficulty.
IDs must be unique on a page. So if you have multiple instances of the same form on a page, you have to change the IDs of the form elements to give unique IDs (Name1, Name2 etc). The problem is that the client-side validation code relies on the value of the ID attribute matching the name of the property on the bound object. But if you are binding multiple forms to different instances of the same object, you can’t have the ID match the object property, or you’ll have invalid HTML.
I came across this situation recently in the game application I mentioned in an earlier post – a version of Chutes/Snakes and Ladders. The user can have as many games as they like – so the same form is needed as many times as there are games:
It’s an unfortunate problem, as IDs shouldn’t matter in validation – Names should. Forms send Name/Value pairs up to the server, not ID/Value pairs. Names do not have to be unique on the page, just inside the form. So if the validation code had been based around the Name attribute matching the object property, the problem would not have arisen.
There are two obvious approaches. The first is to create your own version of the validation code and use Names rather than IDs. That would be the most elegant solution… but it’s also a lot of hard work. The alternative is to implement client-side validation some other way, such as using the jQuery Validation plugin.
I chose to use the Validation plugin – but even so, I still had some extra work to do when creating my widgets in order to avoid having badly formed HTML. The problem is that if you do this:
Then you end up with both ID and Name equal to “Name” (since that was the name of the property on the Model) – and we’re back to badly formed HTML. However you choose to manage your validation, you still need to create unique IDs while leaving the Name as it is (so the server-side method parameter binding works). You can either use the overloaded version of EditorFor() thus:
Or just build it yourself out of HTML and Response.Write thus:
Either approach will end up with the same HTML on the client:
(The object literal in the class attribute contains the validation rules for the Validation plugin, and can be extracted using the Metadata plugin. Both plugins are covered, along with a great deal else, in Learning Tree course jQuery: A Comprehensive Hands-On Introduction.)
In most circumstances, Data Annotations are the right solution for both server and client validation – but once you do something a little unusual (like repeating forms) you may find you have to do a little fancy-footwork to achieve the result you have in mind.
For other related information, check out this course from Learning Tree: