Practical .NET

Separating Validation Code from Business Objects Using DataAnnotations

While you can create classes that contain their own validation code, there are scenarios where it makes sense to separate validation code from the properties it validates using DataAnnotations.

In a recent Practical .NET column, I covered the three interfaces in the .NET Framework that support creating self-validating components --classes that contained their own validation code and integrated with the .NET Framework UI tools.

However, you might prefer not to put your validation code into your business object's class file. There are at least three good reasons for segregating your validation code: It would allow you to test your validation code independently of your class, allow other developers to incorporate your validation logic into other places in their code (rather than where they use your class), and would support UI developers incrementally adding validation to whatever core validation you incorporate into your object (this makes sense if there is some validation related to your class that only applies in some scenarios).

Separating validation code from business classes also reflects actual programming practice. In many cases, your business objects aren't what's used by your UI; instead, developers create a Data Transfer Object (DTO) or ViewModel that represents data from several different business classes and use those classes to drive their application's UI. Separating your validation code from your business class allows UI developers to apply your validation code to their DTOs and ViewModels.

Data Annotations support this separation either by creating your own custom Data Annotation or using the CustomDataAnnotation.

Creating a Custom DataAnnotation
To create your own DataAnnotation object, you add a class that inherits from ValidationAttribute (you'll need a reference to the System.ComponentModel.DataAnnotations library to do this). After that, it's just a matter of overriding the IsValid method with your validation logic; the .NET Framework will take care of passing the value the property is being set to in the method's first parameter (cleverly called value). Your method must return a ValidationResult object containing the error message if an error is found; if no error is found, ValidationResult.Success is returned.

Error messages returned as a ValidationResult from the DataAnnotation are automatically routed to the validation display control in the UI for the property that the Data Annotation is decorating, and to any summary display (e.g. in ASP.NET MVC to an HTML.ValidationMessageFor or an HTML.ValidationSummary).

In theory, if the message applies to multiple items (i.e., for validation tests that compare one property value to another), you can pass the ValidationResult a list of property names and the message will be routed to all of those properties' validation display. In practice, I haven't been able to get this to work, but I haven't really tried, either; repeating the same message in two places on the UI strikes me as a bad idea.

As an example, this DataAnnotation provides a structure that would support all the validation logic required for a CustomerID property on a Customer object:

Public Class CustomerID
    Inherits ComponentModel.DataAnnotations.ValidationAttribute

    Protected Overrides Function IsValid(value As Object, 
         validationContext As
           System.ComponentModel.DataAnnotations.ValidationContext) As
                  System.ComponentModel.DataAnnotations.ValidationResult
	  Dim ErrorMessage As New Text.StringBuilder

          '…validation code that appends error messages to ErrorMessage

        If ErrorMessage.Length > 0 Then
            Dim vr As New ComponentModel.DataAnnotations.
                              ValidationResult(ErrorMessage.ToString)
            Return vr
        Else
            Return ComponentModel.DataAnnotations.
                        ValidationResult.Success
        End If
 
   End Function

There are actually two versions of the IsValid method in the base class you can override: one version that's passed the ValidationContext object and one that's not. The version missing the ValidationContext supports backward compatibility with earlier versions of ASP.NET MVC and can be safely ignored.

The ValidationContext contains several properties that .NET uses to pass information to the method, but the only one I've found useful is the ObjectInstance, which holds a reference to the object that the DataAnnotation is being used in (i.e. the Customer object). You can use the ObjectInstance property to access other members of the class to support validation that involves several properties on the class.

To incorporate your validation logic into your class, you would just decorate your class' property with your DataAnnotation, like this:

<CustomerID()>
Public Property CustomerID() As String

UI developers can now also apply your DataAnnotation to properties in their DTOs or ViewModels.

Complete Separation with CustomValidation
To completely separate your validation logic from any of the frameworks, you can use the DataAnnotations CustomValidation attribute. In this scenario, you simply provide a class with a method that performs the validation and you (or a developer using your code) tie your method to a property using the CustomValidation attribute. When using the CustomValidation attribute, you specify the class and the name of the method containing your validation code, like this:

<ComponentModel.DataAnnotations.CustomValidation(
GetType(CustomerIDValidator), "IsValidForCustomerID")>
Public Property CustomerID() As String

Using the CustomValidation attribute makes sense if the validation code might be used elsewhere than just in your DataAnnotation. However, the method with your validation code has to meet three criteria for it to be used with the CustomValidation attribute. The method must be declared as Static/shared and accept a single parameter -- the value to which the property is being set. The method must also return a ValidationResult object containing the error message if an error is found; or if no error is found, ValidationResult.Success. Here's an example:

Public Class CustomerIDValidator
    Public Shared Function IsValidForCustomerID(value As Object) 
          As ComponentModel.DataAnnotations.ValidationResult
      Dim ErrorMessage As New Text.StringBuilder

       '…validation code

      If ErrorMessage.Length > 0 Then
        Dim vres As New 
          ComponentModel.DataAnnotations.
                      ValidationResult(ErrorMessage.ToString)
        Return vres
      Else
        Return ComponentModel.DataAnnotations.ValidationResult.Success
      End If
    End Function
End Class

Both of these options allow you to separate your validation logic from your class, while still providing the developer using your class with the validation code you expect to be used with your business entity. Even if you expect a developer to use your Data Annotation on the developer's DTO, it's probably still a good idea to decorate your class' property with your Data Annotations. While it means that validation may be done twice (once on when the DTO is updated and again when the developer updates your object from the DTO), it also ensures that your validation is performed. And the point of writing your own validation code for your class is that you can't be too careful.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • AI for GitHub Collaboration? Maybe Not So Much

    No doubt GitHub Copilot has been a boon for developers, but AI might not be the best tool for collaboration, according to developers weighing in on a recent social media post from the GitHub team.

  • Visual Studio 2022 Getting VS Code 'Command Palette' Equivalent

    As any Visual Studio Code user knows, the editor's command palette is a powerful tool for getting things done quickly, without having to navigate through menus and dialogs. Now, we learn how an equivalent is coming for Microsoft's flagship Visual Studio IDE, invoked by the same familiar Ctrl+Shift+P keyboard shortcut.

  • .NET 9 Preview 3: 'I've Been Waiting 9 Years for This API!'

    Microsoft's third preview of .NET 9 sees a lot of minor tweaks and fixes with no earth-shaking new functionality, but little things can be important to individual developers.

  • Data Anomaly Detection Using a Neural Autoencoder with C#

    Dr. James McCaffrey of Microsoft Research tackles the process of examining a set of source data to find data items that are different in some way from the majority of the source items.

  • What's New for Python, Java in Visual Studio Code

    Microsoft announced March 2024 updates to its Python and Java extensions for Visual Studio Code, the open source-based, cross-platform code editor that has repeatedly been named the No. 1 tool in major development surveys.

Subscribe on YouTube