by Jeff Handley via Jeff Handley on 10/12/2010 8:31:05 AM
In my last post, I went over some cross-field validation scenarios and provided some sample code, including a CompareValidatorAttribute. We’ve now covered single-field validation and cross-field validation, but there’s yet another level of validation supported by RIA Services—Entity-Level validation. As you’ll see in this post, entity-level validation is very similar to what we’ve already seen.
Your entity types can declare entity-level validation using either the [CustomValidation] attribute approach where a custom validation method is supplied, or by having an attribute that derives from ValidationAttribute. Either way, to indicate that a validation rule applies at the entity-level (or type-level) as opposed to the property-level, you simply put the attribute on the class itself instead of on a specific property. See how simple this is:
[CustomValidation(typeof(MeetingValidators), "PreventExpensiveMeetings")] public partial class Meeting { ... }
In this example, I’m using the [CustomValidation] attribute approach. Because I rarely find entity-level validation to be reusable across entities, I typically go that route. But if your business rules lead to scenarios where you do in fact have common entity-level validation, you might benefit from creating a derived validation attribute.
Here’s the implementation of my PreventExpensiveMeetings validation method.
/// <summary> /// Ensure that long meetings don't include too many attendees. /// </summary> /// <param name="meeting">The meeting to validate.</param> /// <returns> /// A <see cref="ValidationResult"/> with an error or <see cref="ValidationResult.Success"/>. /// </returns> public static ValidationResult PreventExpensiveMeetings(Meeting meeting) { TimeSpan duration = meeting.End - meeting.Start; int attendees = (meeting.MaximumAttendees + meeting.MinimumAttendees) / 2; int cost = attendees * 50 * duration.Hours; if (cost > 10000) { return new ValidationResult("Meetings cannot cost the company more than $10,000."); } return ValidationResult.Success; }
There are some differences between this validation method and the ones we saw for single-field and cross-field validation:
If you were creating a reusable entity-level validator, you might find that you want to accept the ValidationContext parameter so that you can use the ValidationContext.ObjectType property, or quite possibly additional state information that can be provided by ValidationContext. Similarly, there’s no reason why you can’t have your method specify the MemberName(s) for the ValidationResult; this should be done if you have clear direction to give the user on what field(s) to change to correct the error.
It can be tricky to differentiate between cross-field validation and entity-level validation. In fact, the PreventExpensiveMeetings example is performing cross-field validation. It’s validating values across the Start, End, MinimumAttendees, and MaximumAttendees fields. Previously, we saw validation rules defined that validated the Start/End property pairs and the MinimumAttendees/MaximumAttendees properties. Each of those validators was applied to both properties and users got notification of errors as soon as one of the fields was put into conflict with the other. The PreventExpensiveMeetings scenario is different for a few reasons though, and these are what I look for when deciding whether to implement validation at the property-level or the entity-level.
In the Validation Triggers article, we learned that entity-level validation occurs in a few places:
For each of those triggers, property-level validation is performed first, and only if all properties are valid are entity-level validators invoked. In light of our validation rule relying upon property-level validation being successful, it now makes a lot more sense why this short circuit is in place. Here’s a reminder of the validation stages:
Also mentioned in the Validation Triggers post, DataGrid and DataForm have deep support for validation; this includes entity-level validation. Both of these controls are able to display entity-level validation right out of the box. Here’s what you will see when an entity-level validation error occurs in each of these controls.
If you are not using the DataGrid or DataForm, there’s a trick that can be applied to display a single entity-level validation error when one occurs. This is to have a control on your page that has a binding to the entity itself, and not to a property on the entity. For instance, the following XAML would lead to having a TextBox that will show the first entity-level validation error that occurs on the Meeting that is set to be the DataContext of the Grid.
<Grid> <Grid.DataContext> <my:Meeting /> </Grid.DataContext> <TextBox Text="Schedule a Meeting" DataContext="{Binding}" BorderThickness="0" IsReadOnly="True" HorizontalAlignment="Left" /> </Grid>
The key is having something on the TextBox bound to the Meeting instance using a {Binding} without a Path. This binding can exist on any property, not just Text; in this case, I used the DataContext property. Of course, this XAML is contrived and won’t actually set up the ability to edit a meeting. But using this TextBox declaration in a different context, my page shows the following when I end editing on the meeting:
I am by no means a designer though (can you tell?), so I will stop short of advising you on how best to show your entity-level errors to your end users. If you are interested, Regis Brid’s whitepaper on INotifyDataErrorInfo includes a reusable ValidationErrorViewer control that can be used to show a list of entity-level validation errors, using a trick similar to the {Binding} markup shown above.
We can now see that some occurrences of cross-field validation should really be broken out to the entity-level stage of validation. This allows the validation rule to execute on the entity in a state where all property values are known to be valid, and the logic can be implemented very easily. Declaring the entity-level validation is simple too: just apply a [CustomValidation] attribute to the entity’s class rather than on a property. DataGrid and DataForm will both display entity-level validation errors by default, and you can use a {Binding} trick to get some primitive controls to display an entity-level validation error too.
This article is part of an in-depth series on RIA Services Validation. Here’s the full series:
We have already covered a great deal, but there are plenty of validation topics left to cover. Still to come, we’ll be exploring the power of ValidationContext, demonstrating validation localization, and going on a tangent with the DisplayAttribute. I’ll also provide a validator factory implementation that can consume validation rules from other types so that validation rules from entities can be inherited into a ViewModel!
Original Post: RIA Services Validation: Entity-Level Validation
The content of the postings is owned by the respective author. Silverlight Feeds is not responsible for the contents of the postings. This site is automatically generated and cannot be reviewed for abusive content. If you find abusive content on Silverlight Feeds, please contact us. Designated trademarks and brands are the property of their respective owners. All rights reserved.