Practical .NET

Exploiting the .NET Validation Frameworks

Much of the Microsoft .NET Framework is designed to support creating components: business objects that not only carry their business logic with them from one environment to another, but also carry some additional "meta code" that can be used to drive the presentation layer of whatever application they're being used in.

Ideally, UI developers just wire your components up to their UIs, and those UIs will do the right thing. The .NET Framework contains three interfaces you can leverage to have the validation errors generated by logic in your components automatically integrated into an application's UI. In some environments, the only thing the UI developer needs to do is add a reference to your component. In other environments, the UI developer needs to add some support for displaying the error messages your component generates.

There are lots of benefits to turning classes into components by incorporating validation logic into your business logic. First, putting validation logic -- which is really part of your business rules -- into your business classes lets UI developers concentrate on what's important to the UI (workflow, where and how validation is to be displayed and so on) and ignore what's not (implementing business rules).

Using the tools defined by the .NET Framework also makes your code more efficient because the alternative to implementing these interfaces is to have your business classes raise exceptions. Exceptions are guaranteed to bring the CLR to a halt (every Visual Studio developer knows that when a debugging session takes a long pause it's because an exception has been raised). Components also support consistency through reusability: Self-validating business classes can be reused in a variety of environments, ensuring that your business rules are consistently applied in all applications.

While these interfaces and classes allow your components to automatically integrate with applications written in Windows Presentation Foundation (WPF), Silverlight and ASP.NET MVC, different classes and interfaces are available in each environment. Only one interface (the least sophisticated) functions in all of them. This article will introduce all three interfaces, along with what a UI developer must do to take advantage of validation code. It will also demonstrate some best practices in implementing these interfaces to simplify moving your classes from one environment to another. Furthermore, these practices facilitate integrating your validation code with environments that don't automatically use these interfaces (for instance, ASP.NET).

Integrating with WPF, Silverlight and ASP.NET MVC
The most widely supported validation interface is IDataErrorInfo, which is automatically recognized in the WPF, Silverlight and ASP.NET MVC environments. Implementing the IDataErrorInfo interface requires adding two members to your class, only one of which is actually needed: You can ignore the interface's Error property, which returns errors for the object as a whole (I typically just return Nothing or null from this property). The more useful member is an indexer property (called Item in Visual Basic and this in C#). The indexer property is called by the host environment, passing the name of the property currently being processed by the host environment. It's your responsibility to return a string with the errors related to the property.

Because a property might have multiple errors associated with it, I hold my errors in a Dictionary (called _Errors) where each entry consists of a property name and a list of errors for that property. I also have my classes implement the INotifyPropertyChanged event, to let the UI know when a property in my class is updated. (For more on the INotifyPropertyChanged interface and why you should be using it as part of "componentizing" your classes, see my online Practical .NET column, "Integrating with the .NET Framework UI Controls".) A self-validating component called Customer that implements the IDataErrorInfo and INotifyPropertyChanged interfaces would start like this:

Public Class Customer
  Implements ComponentModel.INotifyDataErrorInfo
  Implements ComponentModel.INotifyPropertyChanged

Private _Errors As New Dictionary(Of String, List(Of String))

Assuming that the class has a property called CompanyID that has validation rules associated with it, my next step is to create a method (which I've called IsValidFor-CompanyID) to hold the property's validation code. My method accepts the value to be checked, updates the _Errors collection and returns True if any errors are found. I declare the method as public for flexibility. Making the method public means it can be used by external code to check for valid CustomerID values.

In the setter for the CompanyID property, I call my validation method, passing the CustomerID (this code also uses the INotifyPropertyChanged interface's Property-Changed event to notify the UI of changes to the property), as shown in Listing 1.

Now it's just a matter of managing error messages in my validation method.

Generating Error Messages
In my IsValidForCompanyID method, I first remove any existing errors for the CompanyID property from my _Errors collection:

Public Function IsValidForCustomerID(value As String) As Boolean
  Dim NameError As New List(Of String)

  If _Errors.ContainsKey("CustomerID") Then
    _Errors.Remove("CustomerID")
End If

Now I go on to validate the value passed. First I create a List that I can add error messages to. When I find an error, I add the appropriate message to the list of errors. Typical code looks like this:

Dim NameError As New List(Of String)

If value = String.Empty Then
  NameError.Add("Customer ID can not be set to an empty string")
End If

At the end of the method, I check to see if any errors were added. If there were, I add the collection of errors I found to my _Errors collection and return False to support using this method in other scenarios:

If NameError.Count > 0 Then
  If Notify Then
    _Errors.Add("CustomerID", NameError)
End If
  Return False
Else
  Return True
End If

Finally, in the indexer property required by the interface, I return the errors for the property whose name is passed to the indexer. Because IDataErrorInfo only supports one message per property, I concatenate all the error messages for the property into a single string, as shown in Listing 2.

If you're using the Entity Framework 4, you can integrate the IDataErrorInfo interface code with the classes generated by the Entity Framework. If your Entity Framework model generates a class called Customer that you want to add the interface to, first create a matching partial Customer class that implements the interface:

Partial Public Class Customer
  Implements ComponentModel.IDataErrorInfo

After adding your validation method and the two members required by the interface, you just need to replace the default partial OnCustomerIDChanging method generated by the Entity Framework with a call to your validation method. Code like this does the trick:

Private Sub OnCustomerIDChanging(value As Global.System.String)
  IsValidForCustomerID (value, True) 
End Sub

Integrating with the UI
To use your self-validating component in WPF or Silverlight, a UI developer will typically bind your component's properties to a control such as a TextBox using two-way binding to support updating the property from the UI. The developer will also set the binding's UpdateSourceTrigger property to some event to control when the property is updated (for a TextBox, I typically use the LostFocus event so that I don't validate on each of the user's keystrokes). The only thing that the UI developer has to do in order to take advantage of your IDataErrorInfo interface is set the binding's ValidatesOnDataErrors property to True:

<TextBox ... 
  Text="{Binding 
    Mode=TwoWay,
    Path=CustomerID,
    UpdateSourceTrigger=LostFocus,
    ValidatesOnDataErrors=true}" />

Unfortunately, when an error is found, the TextBox only displays a red border -- the related error message isn't displayed. One solution is to have the error message displayed when the user hovers their mouse over the TextBox. To implement that, the UI developer will need to add a new style to either the Windows resources (as I've done in the following example) or to the Application's resources. That code requires a little explanation.

When your IDataErrorInfo code returns an error, it updates the .NET Validation ob-ject. My style ties to any TextBox to create a Trigger that fires when the Validation object's HasError property is set to true. When that happens, the style binds the ToolTip property on the UI object that fired the Trigger to the first error in the Validation object's Errors collection -- which is where the Validation object stores your error messages:

<Window.Resources>
  <Style TargetType="{x:Type TextBox}">
    <Style.Triggers>
      <Trigger Property="Validation.HasError" Value="true">
        <Setter Property="ToolTip" 
          Value="{Binding RelativeSource={x:Static RelativeSource.Self},
            Path=(Validation.Errors)[0].ErrorContent}"/>
      </Trigger>
    </Style.Triggers>
  </Style>
</Window.Resources>

In ASP.NET MVC, the IDataErrorInfo interface integrates with the ASP.NET MVC ModelState IsModelValid property. Code like this in a controller will check the results of your IDataErrorInfo code:

<HttpPost()>
Function Customer(Cust As Customer) As ActionResult
  If ModelState.IsValid = False Then
    Return View(cust)
  End If
  Return View("UpdateSuccess",cust)
End Function

The ASP.NET MVC View developer can decide where and how to display your error messages by including either (or both) the ValidationSummary helper (which will display all errors returned from the indexer property) or the ValidationMessageFor helper (which will display just the errors for a single property). This example uses both, specifying the CustomerID property in the ValidationMessageFor helper:

@Html.ValidationSummary()
@Html.TextBoxFor(Function(m) m.CustomerID)
@Html.ValidationMessageFor(Function(m) m.CustomerID)

Integrating with Silverlight 4
In addition to supporting IDataErrorInfo, Silverlight 4 includes the INotifyDataErrorInfo interface, which supports asynchronous and server-side validation (the interface was designed to support WCF RIA Services). Even if you're not using WCF RIA Services, implementing INotifyDataErrorInfo instead of IDataErrorInfo means that a time-consuming validation process (one that involves calling a Web Service to validate data, for instance) won't freeze the application's UI. However, this interface will only be used by a Silverlight 4 application.

INotifyDataErrorInfo adds two members to your component: a property called HasErrors and a method called GetErrors. You also get an event called ErrorsChanged that signals to Silverlight there are errors to report. When notified, Silverlight will read the interface's HasErrors property -- it's your responsibility to return True if you have errors. Having both the event and the property signal for errors might seem redundant. However, you use the ErrorsChanged event not only to signal to Silverlight that you have a validation error, but also to signal that any prior errors have been resolved.

When Silverlight has determined that there are errors, it will call the interface's GetErrors method, passing the name of a property to retrieve the error messages for that property. In the GetErrors method you must return the current set of errors for that property.

Your GetErrors method must return some class that implements IEnumerable. In the code in Listing 3, I've used the same Dictionary as I used with IDataErrorInfo. Other than implementing INotifyDataErrorInfo instead of IDataErrorInfo, the start of the Customer class is identical to a class implementing IDataErrorInfo. The setter for the CompanyID property is also unchanged from the code that implements IDataErrorInfo.

However, in the IsValidForCompanyID method, when I remove any existing errors for the CompanyID from my _Errors collection, I raise the ErrorsChanged event (removing errors counts as a change to the errors, after all). The first parameter passed to the event is, as usual, a reference to the component raising the event. The second parameter must be a DataErrorsChangedEventArgs object holding the name of the property whose errors are being changed.

To support using this method in other scenarios, I have my validation method accept two parameters. As before, it accepts the value to be checked -- but I add a Notify parameter that, when set to False, causes the code to skip firing the ErrorsChanged event. Typical code for my validation method now looks like Listing 3.

At the end of the IsValidForCustomerID method, if any errors were found, I now also raise the ErrorsChanged event after adding any errors to my _Errors collection:

If NameError.Count > 0 Then
  _Errors.Add("CustomerID", NameError)
  If Notify Then
    RaiseEvent ErrorsChanged(Me, 
      New ComponentModel.DataErrorsChangedEventArgs("CustomerID"))
  End If
  Return False
Else
  Return True
End If

With those changes made, it's time to implement the two members required by the interface. The HasErrors property just has to check to see if there's anything in the _Errors Dictionary to decide whether or not to return True:

Public ReadOnly Property HasErrors As Boolean 
  Implements System.ComponentModel.INotifyDataErrorInfo.HasErrors
  Get
    If _Errors.Count > 0 Then
      Return True
    Else
      Return False
    End If
  End Get
End Property

The GetErrors method just has to return the appropriate collection of error messages from the Dictionary:

Public Function GetErrors(propertyName As String) 
    As System.Collections.IEnumerable 
    Implements System.ComponentModel.INotifyDataErrorInfo.GetErrors
  Return _Errors(propertyName)
End Function

Integrating with the Silverlight 4 UI
The minimum markup a Silverlight 4 UI developer would use to bind a TextBox to your property with two-way binding would look like this:

<TextBox ... Text="{Binding 
                       Mode=TwoWay,
                       Path=CustomerID}"/>

And that's all that's necessary for the UI to automatically pick up and display the errors your component generates. If the user enters invalid data, the TextBox bound to the property will be highlighted in red and an arrow will appear in the upper-right-hand corner of the TextBox. When the user clicks on the arrow in the corner of the TextBox, the first error message for the property bound to the TextBox will be displayed in a tooltip automatically -- there's no need to add a special style.

However, while the interface is returning multiple messages, this markup only displays the first message for the property. If the UI developer wants to put in some additional work, the UI can be extended to display all the messages associated with the property. First, the developer needs to set the NotifyOnValidationError property on the TextBox to True:

<TextBox ... Text="{Binding 
                       Mode=TwoWay,
                       Path=CustomerID,
                       NotifyOnValidationError=True}"/>

The UI developer must next add a reference to the Sys-tem.Windows.Controls.Data.Input library to the project, and set up a namespace for the library in the XAML file:

<UserControl ...
  xmlns:wc=
    "clrnamespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input"
...

Finally, the developer adds a SummaryValidation control to the page, which will display all the messages generated by your component:

<wc:ValidationSummary/>

Validation in ASP.NET MVC 3
If you're using ASP.NET MVC 3, there's a third interface available: IValidatableObject. IValidatableObject is the simplest interface -- it just adds a single method called Validate to your class. The Validate method is called once for the object being displayed (rather than once for each property bound to a control in the view). Because the Validate method isn't tied to a specific property, it's a good choice for implementing validation that involves multiple properties and can be used with IDataErrorInfo.

The IValidatableObject interface has one significant issue: its Validate method is only called after any validation implemented through other mechanisms has passed (for instance, IDataErrorInfo and DataAnnotations). This means that a user could get a set of error messages, resolve all of them … and then get hit with the message (or messages) generated by your Validate method. There's nothing a UI designer using your component can do to prevent this. Restricting your Validate method to cross-property error messages (or putting all of your validation logic in the Validate method) will reduce the UI developer's aggravation.

The first parameter (called ValidateContext) is passed a ValidationContext object that has several properties, most of which aren't of any use in the ASP.NET MVC environment (the ValidationContext class is also used in, for instance, DataAnnotations, where your validation code is separated from the class that it's validating).

The method should return a collection of ValidationResult objects, each of which holds an error message and, optionally, a list of affected properties. Passing the property names ensures that the ValidationFor HTML helper displays the messages. If you omit the property names, your error messages will only appear in the ValidationSummary HTML helper. If you don't have any errors to report, you should return an empty collection.

This example enforces a rule that CustomerIDs for customers in the SPAIN region must be less than 10 characters long. If the rule is broken, the code creates a ValidationResult object passing the error message and an array holding the names of the two properties involved (CustomerID and CustomerRegion). In this sample I pass the array of strings as an anonymous object initialized with the property names:

Dim errs As New List(Of ComponentModel.DataAnnotations.ValidationResult)
Dim vr As ComponentModel.DataAnnotations.ValidationResult

If Me.CustomerID.Length > 10 AndAlso
  Me.CustomerRegion = "SPAIN" Then
    vr = New ComponentModel.DataAnnotations.ValidationResult(
      "Spanish Customer IDs must be less than 10 characters",
        New String() {"CustomerID", "CustomerRegion"})
    errs.Add(vr)
End If

Return errs

Validation Choices
As you can see, the .NET Framework provides a rich (too rich?) set of options for you to convert your classes into components, at least as far as displaying validation errors in an application's UI. If your component is going to be used in multiple environments, you'll want to implement the IDataErrorInfo interface -- it's the only tool supported in every environment. If your component will only be used in a Silverlight 4 environment, implementing the INotifyData¬ErrorInfo provides more functionality in the UI and requires less work by the UI developer (it also supports using your component in WCF RIA Services). For cross-property validation in ASP.NET MVC 3, you can add IValidatableObject to your component.

That's a lot of potential interfaces to implement, but provided you use the framework I've suggested here (and put your validation code in a separate method), implementing multiple interfaces to support multiple environments isn't out of the question. Even if your component isn't going to be used in an environment that supports these interfaces, implementing them still makes life easier for UI designers by providing a well-defined interface. Whichever interface you pick, you'll simplify the lives of UI designers while ensuring consistency in applying business rules across your organization.

About the Author

Peter Vogel is a principal in PH&V Information Services, specializing in Web development with expertise in SOA, client-side development, and user interface design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His most recent book ("rtfm*") is on writing effective user manuals, and his blog on language and technical writing can be found at rtfmphvis.blogspot.com.

comments powered by Disqus

Reader Comments:

Add Your Comments Now:

Your Name:(optional)
Your Email:(optional)
Your Location:(optional)
Comment:
Please type the letters/numbers you see above

.NET Insight

Sign up for our newsletter.

I agree to this site's Privacy Policy.