다음을 통해 공유


.NET: Defensive data programming (Part 4) Data Annotation

Introduction

Validating data in windows forms application is generally done by determining if the information entered by a user is the proper type e.g. price of a product is type decimal, the first and last name of a person entered are not empty strings. In a conventional application, a developer will have logic in a button click event to validate all information entered on a form which has worked for many, yet this logic is locked into a single form/window.  For ASP.NET MVC Data Annotations are used to validate against a model (a class) using built-in attributes to validate members in a model/class along with the ability to override built-in attributes and create custom attributes. In this article, exploration will be done to show how to use built-in and custom Data Annotation Attributes in windows forms applications.

.NET: Defensive data programming (part 1) 
.NET: Defensive data programming (Part 2) 
.NET: Defensive data programming (Part 3) 

ASP.NET MVC Data Annotation

Using these attributes provide methods to display meaningful text for displaying field names and error text as shown below. Note the red text is the text to display when a rule for validation fails which is done for you via the validator.

 The view where on the first line PersonModel is the class container for validating against. Pressing the submit button will activate the validation.

@model TextBox_Validation_MVC.Models.PersonModel
 
@{
    Layout = null;
}
 
<!DOCTYPE html>
 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
    <style type="text/css">

        body {
            font-family: Arial;
            font-size: 10pt;
        }
        .error {
            color: red;
        }
    </style>
</head>
<body>
    <div class="container-fluid">
        @using (@Html.BeginForm("Index", "Home", FormMethod.Post))
        {
            <h2>Simple data validation</h2>
            <h3>Defensive programming part 3</h3>
            <table>
                <tr>
 
                    <td>
                        @Html.Label("First name",
                        new {@class = "float-right form-control-sm"})
                </td>
 
                <td>
                    @Html.TextBoxFor(pm => pm.FirstName,
                    new {@class = "form-control-sm"})
            </td>
 
            <td>
                @Html.ValidationMessageFor(pm => pm.FirstName,
                "",
                new {@class = "error"})
        </td>
    </tr>
    <tr>
 
        <td>
            @Html.Label("Last name",
            new {@class = "float-right form-control-sm"})
    </td>
 
    <td>
        @Html.TextBoxFor(pm => pm.LastName,
        new {@class = "form-control-sm"})
    </td>
 
    <td>
        @Html.ValidationMessageFor(m => m.LastName,
        "",
        new {@class = "error"})
    </td>
    </tr>
    <tr>
 
        <td>
            @Html.Label("Ssn",
            new {@class = "float-right form-control-sm"})
    </td>
 
    <td>
        @Html.TextBoxFor(pm => pm.Ssn,
        new {@class = "form-control-sm"})
    </td>
 
    <td>
        @Html.ValidationMessageFor(m => m.Ssn,
        "", new {@class = "error"})
    </td>
 
    </tr>
 
    <tr>
        <td></td>
        <td><input type="submit" class="btn btn-primary" value="Submit" /></td>
        <td></td>
    </tr>
    </table>
}
    </div>
 
</body>
@Scripts.Render("~/bundles/jquery")
</html>

Windows Forms Data Annotation

When working with data annotations in windows forms functionality for displaying field names as done in ASP.NET MVC is not possible without a good deal of coding which is beyond the scope of this article, similarly for displaying error messages as done in ASP.NET MVC unless a ErrorProvider component is implemented to display error messages by cycling through each error message and set the error text for each control (TextBox, ComboBox etc). Here the focus will be on displaying error messages in a dialog.

Using the login shown above here is the Windows form version.

Step 1, add a reference to System.ComponentModel.DataAnnotations to the project. Add the following three classes to the project.

EntityValidationResult
EntityValidator
ValidationHelper

Step 2, add a class to represent data which is to be collected in a windows form.

Imports System.ComponentModel.DataAnnotations
 
Namespace Classes
    Public Class  Taxpayer
 
        <RegularExpression("^\d{9}|\d{3}-\d{2}-\d{4}$",
                           ErrorMessage:="Invalid Social Security Number")>
        <Required(ErrorMessage:="Contact {0} is required"),
            DataType(DataType.Text)>
        Public Property  SSN() As  String
 
        <Required(ErrorMessage:="{0} is required"),
            DataType(DataType.Text)>
        Public Property  FirstName() As  String
 
        <Required(ErrorMessage:="{0} is required"),
            DataType(DataType.Text)>
        Public Property  LastName() As  String
    End Class
End Namespace

For the property SSN, the attribute  RegularExpression is the rule to validate there is a good SSN which is a generic rule as for a full validation of an SSN there can be many rules such as.

A Social security number cannot

  • Contain all zeroes in any specific group (ie 000-##-####, ###-00-####, or ###-##-0000)
  • Begin with ’666′.
  • Begin with any value from ’900-999′
  • Be ’078-05-1120′ (due to the Woolworth’s Wallet Fiasco)
  • Be ’219-09-9999′ (appeared in an advertisement for the Social Security Administration)

To be able to run all of these rules a special validator is in order which will be gone over later.

Step 3, create a new form, SocialSecurityForm.vb with 

  • TextBox for FirstName 
  • TextBox for LastName
  • TextBox for SSN
  • Button for validation

To validate entries the following code is used in the validate button click event. In the code shown below the import, statements point to classes in the project (which is included in the source code for this article).

Imports BasicClassValidation.Classes
Imports BasicClassValidation.LanguageExtensions
Imports BasicClassValidation.Validators
 
Namespace Forms
    Public Class  SocialSecurityForm
        Private Sub  LogInButton_Click(sender As Object, e As  EventArgs) _
            Handles LogInButton.Click
 
            '
            ' Note that many people like to use SSN format 111-11-1111 so we remove dashes.
            ' Alternately a mask may be used on the TextBox.
            '
            Dim taxpayer As New  Taxpayer With
                    {
                        .FirstName = FirstNameTextBox.Text,
                        .LastName = LastNameTextBox.Text,
                        .SSN = SsnTextBox.Text.Replace("-", "")
                    }
 
            Dim validationResult As EntityValidationResult =
                    ValidationHelper.ValidateEntity(taxpayer)
 
            If validationResult.HasError Then
                MessageBox.Show(validationResult.ErrorMessageList())
            Else
                DialogResult = DialogResult.OK
            End If
 
        End Sub
    End Class
End Namespace

Notes:

For SSN validation 

<RegularExpression("^\d{9}|\d{3}-\d{2}-\d{4}$",
                   ErrorMessage:="Invalid Social Security Number")>
<Required(ErrorMessage:="Contact {0} is required"),
    DataType(DataType.Text)>
Public Property  SSN() As  String

RegularExpression is the rule to validate while ErrorMessage is the text to display which data entered does not match the rule and DataType(DataType.Text) is the type for the property. If the field name can be shown and be meaningful to the user the following can be used where {0} at runtime is replaced with the field name, in this case SSN,

ErrorMessage:="Invalid {0}")

For fields such as

<Required(ErrorMessage:="{0} is required"),
    DataType(DataType.Text)>
Public Property  FirstName() As  String

The error message will be FirstName is required. This may not be pleasing to some. Included in the source code is the following language extension method.

Public Module  StringExtensions
    <Runtime.CompilerServices.Extension>
    Public Function  SplitCamelCase(sender As String) As  String
        Return Regex.Replace(
            Regex.Replace(sender,
                "(\P{Ll})(\P{Ll}\p{Ll})", "$1 $2"),  "(\p{Ll})(\P{Ll})",  "$1 $2")
    End Function
End Module

The language extension is used to take field names such as FirstName and split them per capital letters so FirstName turns into First Name. Still in the social security form.

Dim validationResult As EntityValidationResult =
        ValidationHelper.ValidateEntity(taxpayer)
 
If validationResult.HasError Then
    MessageBox.Show(validationResult.ErrorMessageList())
Else
    DialogResult = DialogResult.OK
End If

ErrorMessageList is a language extension method which iterates through the validator errors, collects each error, splits fields like FirstName and removes any double spaces from the error message and places the error messages into a StringBuilder then returns a string to present in a MessageBox.

Imports System.ComponentModel.DataAnnotations
Imports System.Text
Imports System.Text.RegularExpressions
Imports BasicClassValidation.Validators
 
Namespace LanguageExtensions
    Public Module  ValidatorExtensions
        ''' <summary>
        ''' Separates tokens with a space e.g. ContactName becomes Contact Name
        ''' </summary>
        ''' <param name="sender"></param>
        ''' <returns></returns>
        <Runtime.CompilerServices.Extension>
        Public Function  SanitizedErrorMessage(sender As ValidationResult) As String
            Return Regex.Replace(sender.ErrorMessage.SplitCamelCase(),  " {2,}", " ")
        End Function
        ''' <summary>
        ''' Place all validation messages into a string with each validation message on one line
        ''' </summary>
        ''' <param name="sender"></param>
        ''' <returns></returns>
        <Runtime.CompilerServices.Extension>
        Public Function  ErrorMessageList(sender As EntityValidationResult) As String
 
            Dim sb As New  StringBuilder
            sb.AppendLine("Validation issues")
 
            For Each  errorItem As  ValidationResult In  sender.Errors
                sb.AppendLine(errorItem.SanitizedErrorMessage)
            Next
 
            Return sb.ToString()
 
        End Function
    End Module
End Namespace

Compare attribute


Using the compare attribute provides an easy method to confirm two properties have the same value. A good example is when a new user is registering and must confirm their passwords.

<Required(ErrorMessage:="{0} is required"), DataType(DataType.Text)>
<StringLength(12, MinimumLength:=6)>
Public Property  Password() As  String
<Compare("Password", ErrorMessage:="Passwords do not match, please try again")>
<StringLength(12, MinimumLength:=6)>
Public Property  PasswordConfirmation() As String

The complete code sample for the above is found in this form. Note that the same validator EntityValidationResult used in the Taxpayer example above is used in this code sample, this keeps everything consistent no matter which class is being validated.

Imports BasicClassValidation.Classes
Imports BasicClassValidation.LanguageExtensions
Imports BasicClassValidation.Validators
 
Namespace Forms
 
    Public Class  LoginForm
        Private Sub  LogInButton_Click(sender As Object, e As  EventArgs) _
            Handles LogInButton.Click
 
            Dim login As New  CustomerLogin With
                    {
                        .Name = UserNameTextBox.Text,
                        .Password = PasswordTextBox.Text,
                        .PasswordConfirmation = PasswordConfirmTextBox.Text,
                        .EntryDate = EntryDateDateTimePicker.Value
                    }
 
            Dim validationResult As EntityValidationResult = ValidationHelper.ValidateEntity(login)
 
            If validationResult.HasError Then
                MessageBox.Show(validationResult.ErrorMessageList())
            Else
                DialogResult = DialogResult.OK
            End If
 
        End Sub
    End Class
End Namespace

If the validation passes the form (shown modal) is closed where the calling form can get data from the TaxPayer  (in a real app the instance of CustomerLogin would be a public read only property of the form).

StringLength attribute

This attribute provides the ability to specify the max and min length of a string. For instance, a field FirstName in a database table can only store 50 characters, the following will check the value passed to see if the length exceeds this length. For the min this prevents a user entering a space. The full rule is 

  • Required indicates the field can not be empty.
  • MinimumLength indicates at least three characters are required.
  • 50 is the max length.
<Required(ErrorMessage:="{0} is Required"),
    StringLength(50, MinimumLength:=3)>
Public Property  FirstName() As  String

Besides StringLength, MaxLength(X) can be used to specify a max length of a string.

Range attribute

Use this attribute to constrain the range a property can fall into. 

Public Class  Product
    <Range(10, 1000, ErrorMessage:="Value for {0} must be between {1} and {2}.")>
    Public Weight As Object
 
    <Range(300, 3000)>
    Public ListPrice As Object
 
    <Range(GetType(DateTime), "1/2/2019",  "3/4/2019",
           ErrorMessage:="Value for {0} must be between {1} and {2}")>
    Public SellEndDate As Object
End Class

Although the range attribute handles numerics great there may be times when a date rule is needed that just does not fit into the range e.g. a property value can not be a weekend date. In this case a custom class is required.

Namespace Rules
    ''' <summary>
    ''' Checks for if a date falls on a weekend.
    ''' </summary>
    Public Class  CustomerWeekendValidation
        Public Shared  Function WeekendValidate(senderDate As Date) As  ValidationResult
            Return If(senderDate.DayOfWeek = DayOfWeek.Saturday OrElse
                      senderDate.DayOfWeek = DayOfWeek.Sunday,
                      New ValidationResult("The weekend days are not valid"),
                      ValidationResult.Success)
        End Function
    End Class
End Namespace

Sample implementation specifying the rule using CustomValidation class. See this in use in the following form.

<CustomValidation(GetType(CustomerWeekendValidation),
                  NameOf(CustomerWeekendValidation.WeekendValidate))>
Public Property  EntryDate() As  Date

Dealing with Enumerations

One of the most difficult items to validate on are Enum types. In short for validating Enum types using the following as an example

Namespace Enumerations
    Public Enum  BookCategory
        SpaceTravel
        Adventure
        Romance
        Sports
        Automobile
    End Enum
End Namespace

Change to the following where there is a zero member.

Namespace Enumerations
    Public Enum  BookCategory
        [Select] = 1
        SpaceTravel = 2
        Adventure = 3
        Romance = 4
        Sports = 5
        Automobile = 6
    End Enum
End Namespace

Besides numbering members an additional member has been added to provide visual confirmation that a selection is required. The following demonstrates this in use where a book class shown below uses a custom rule, RequiredEnumAttruibute which checks first if a value has been set then if not empty validates it's a valid enum..

Namespace Classes
    Public Class  Book
        <Required(ErrorMessage:="{0} is required")>
        Public Property  Title() As  String
 
        <Required(ErrorMessage:="{0} is required")>
        Public Property  ISBN() As  String
 
        <RequiredEnum(ErrorMessage:="{0} is required.")>
        Public Property  Category() As  BookCategory
 
        <ListHasElements(ErrorMessage:="{0} must contain at lease one note")>
        Public Property  NotesList() As  List(Of String)
    End Class
End Namespace

Here a form is setup to collect information on a book where categories are displayed in a ComboBox. Pressing the validate button runs the same validation as in prior examples with the difference of converting the Category selection from string to Enum then determining if a valid selection has been made where "Select" is not valid.

Namespace Forms
 
    Public Class  ListAndEnmForm
        Private Sub  ListAndEnmForm_Load(sender As Object, e As  EventArgs) Handles  MyBase.Load
            CategoryComboBox.Items.AddRange([Enum].GetNames(GetType(BookCategory)))
            CategoryComboBox.SelectedIndex = 0
        End Sub
 
        Private Sub  ValidateButton_Click(sender As Object, e As  EventArgs) Handles  ValidateButton.Click
 
            Dim category = DirectCast([Enum].Parse(GetType(BookCategory), CategoryComboBox.Text), BookCategory)
 
            Dim book = New Book() With
                    {
                    .Title = BookTitleTextBox.Text,
                    .ISBN = IsbnTextBox.Text,
                    .Category = If(category = BookCategory.Select, Nothing, category),
                    .NotesList = TextBox1.Lines.ToList()
                    }
 
            Dim validationResult = ValidationHelper.ValidateEntity(book)
 
            If validationResult.HasError Then
                MessageBox.Show(validationResult.ErrorMessageList())
            Else
                DialogResult = DialogResult.OK
            End If
 
        End Sub
    End Class
End Namespace

List properties

Not all properties of a class a numeric, string, date or enum, there are cases where a property may be a list. The following custom class which inherits from the class ValidationAttribute which is the base class for creating custom validation rules.

Namespace Rules
    Public Class  ListHasElements
        Inherits ValidationAttribute
 
        Public Overrides  Function IsValid(sender As Object) As  Boolean
 
            If sender Is Nothing  Then
                Return False
            End If
 
            If IsList(sender) Then
                Dim result = CType(sender, IEnumerable).Cast(Of Object)().ToList()
                Return result.Any()
            Else
                Return False
            End If
 
        End Function
    End Class
End Namespace

Support method

Namespace HelperModules
    Public Module  ObjectHelper
        Public Function  IsList(sender As  Object) As Boolean
            If sender Is Nothing  Then
                Return False
            End If
            Return TypeOf  sender Is  IList AndAlso
                   sender.GetType().IsGenericType AndAlso
                   sender.GetType().GetGenericTypeDefinition().IsAssignableFrom(GetType(List(Of )))
        End Function
    End Module
End Namespace

IsList function determines if sender (of type object) is of type list and can represent List(Of. If so checks using the extension method Any to see if the list has any items. Validation passes if the sender is assigned, is a list and has at least one item.

Implemented in a class property NoteList of List of String.

Namespace Classes
    Public Class  Book
        <Required(ErrorMessage:="{0} is required")>
        Public Property  Title() As  String
 
        <Required(ErrorMessage:="{0} is required")>
        Public Property  ISBN() As  String
 
        <RequiredEnum(ErrorMessage:="{0} is required.")>
        Public Property  Category() As  BookCategory
 
        <ListHasElements(ErrorMessage:="{0} must contain at lease one note")>
        Public Property  NotesList() As  List(Of String)
    End Class
End Namespace

Although this article is VB.NET here is a simplified version done in C#.

namespace DataValidatorLibrary.CommonRules
{
    public class ListHasElements : ValidationAttribute
    {
        public override bool IsValid(object sender)
        {
            if (sender == null)
            {
                return false;
            }
 
            if (sender.IsList())
            {
                var result = ((IEnumerable)sender).Cast<object>().ToList();
                return result.Any();
            }
            else
            {
                return false;
            }
        }
    }
}

Using a language extension (where VB.NET can not have extension methods against type Object)

namespace DataValidatorLibrary.LanguageExtensions
{
    public static  class ObjectExtensions
    {
        public static  bool IsList(this object  sender)
        {
            if (sender == null) return  false;
            return sender is IList &&
                   sender.GetType().IsGenericType &&
                   sender.GetType().GetGenericTypeDefinition().IsAssignableFrom(typeof(List<>));
        }
    }
}

Phone number validation

Although there is a standard attribute for phone type, this can be another property that may need special consideration. As with the last example, a custom class can be created to apply special rules as per below.

Imports System.ComponentModel.DataAnnotations
 
Namespace Rules
    ''' <summary>
    ''' Provides custom rule for phone number rather than using [Phone]
    ''' </summary>
    Public Class  CheckPhoneValidationAttribute
        Inherits ValidationAttribute
 
        Public Overrides  Function IsValid(value As Object) As  Boolean
            If value Is Nothing  Then
                Return False
            End If
 
            Dim convertedValue As String  = value.ToString()
 
            Return (Not String.IsNullOrWhiteSpace(convertedValue)) AndAlso
                   IsDigitsOnly(convertedValue) AndAlso  convertedValue.Length <= 10
 
        End Function
        Private Function  IsDigitsOnly(str As  String) As Boolean
 
            For Each  c In  str
                If c < "0"c OrElse  c > "9"c  Then
                    Return False
                End If
            Next
 
            Return True
 
        End Function
    End Class
End Namespace

In C#

using System.ComponentModel.DataAnnotations;
 
namespace DataValidatorLibrary.CommonRules
{
    /// <summary>
    /// Provides custom rule for phone number rather than using [Phone]
    /// </summary>
    public class  CheckPhoneValidationAttribute : ValidationAttribute 
    {
        public override  bool IsValid(object value)
        {
            /*
             * VS2017 or higher
             */
            bool IsDigitsOnly(string str)
            {
                foreach (var c in str)
                {
                    if (c < '0'  || c > '9')
                        return false;
                }
 
                return true;
            }
 
            if (value == null)
            {
                return false;
            }
 
            string convertedValue = value.ToString();
 
            return !string.IsNullOrWhiteSpace(convertedValue) && 
                   IsDigitsOnly(convertedValue) && 
                   convertedValue.Length <= 10;
        }
    }
}

ErrorProvider implementation


So far all error messages have been displayed in a MessageBox, another option is to place an ErrorProvider on the form that requires validation. In the code block which follows ErrorMessageList has been used to show validation error messages in a MessageBox while ErrorItemList creates a list of ErrorContainer which stores the property name in PropertyName member of the property in the class which is being validated and ErrorMessage member contains the error message for the validation of the control.

Namespace LanguageExtensions
    Public Module  ValidatorExtensions
        ''' <summary>
        ''' Separates tokens with a space e.g. ContactName 
        ''' becomes Contact Name
        ''' </summary>
        ''' <param name="sender"></param>
        ''' <returns></returns>
        <Runtime.CompilerServices.Extension>
        Public Function  SanitizedErrorMessage(sender As ValidationResult) As String
            Return Regex.Replace(sender.ErrorMessage.SplitCamelCase(),  " {2,}", " ")
        End Function
        ''' <summary>
        ''' Place all validation messages into a string with 
        ''' each validation message on one line
        ''' </summary>
        ''' <param name="sender"></param>
        ''' <returns></returns>
        <Runtime.CompilerServices.Extension>
        Public Function  ErrorMessageList(sender As EntityValidationResult) As String
 
            Dim sb As New  StringBuilder
            sb.AppendLine("Validation issues")
 
            For Each  errorItem As  ValidationResult In  sender.Errors
                sb.AppendLine(errorItem.SanitizedErrorMessage)
            Next
 
            Return sb.ToString()
 
        End Function
        <Runtime.CompilerServices.Extension>
        Public Function  ErrorItemList(sender As EntityValidationResult) As List(Of ErrorContainer)
            Dim itemList As New  List(Of ErrorContainer)
 
            For Each  errorItem As  ValidationResult In  sender.Errors
                itemList.Add(New ErrorContainer() With
                {
                    .PropertyName = errorItem.MemberNames.FirstOrDefault(),
                    .ErrorMessage = errorItem.SanitizedErrorMessage
                })
            Next
 
            Return itemList
 
        End Function
    End Module
End Namespace

In the button used to validate data entered, the following line clears each TextBox error message using a language extension method.

Descendants(Of TextBox)().ToList().
    ForEach(Sub(ctr) ErrorProvider1.SetError(ctr, ""))

Next an instance of the Contact Class is populated (as done on prior examples)

Dim contact = New Contact() With {
            .FirstName = FirstNameTextBox.Text,
            .LastName = LastNameTextBox.Text,
            .PersonalEmail = PersonalEmailTextBox.Text,
            .BusinessEmail = BusinessEmailTextBox.Text,
            .Phone = PhoneTextBox.Text
        }

Next the validator is executed

Dim validationResult = ValidationHelper.ValidateEntity(contact)

This is followed  by

  1. Obtaining a list of error which contain property name and error message for each control that failed validation.
  2. Obtain all TextBox controls into a list using a language extension method.
  3. Iterate the error list from step 1, find the control associated with the error.
  4. Set the error message for each control using the ErrorProvider.

If there are no failures on validation add a new Contact to the BindingSource which is then displayed in a DataGridView.

If validationResult.HasError Then
 
    Dim errorItemList As List(Of ErrorContainer) = validationResult.ErrorItemList()
 
    Dim controlsToValidate = Descendants(Of TextBox)().
            Where(Function(c) c.Tag IsNot Nothing).
            Select(Function(ctr) New  With {.Control = ctr, .Tag = CStr(ctr.Tag)})
 
    '
    ' Iterate errors, set error text via the ErrorProvider component
    '
    For Each  ec As  ErrorContainer In  errorItemList
        Dim current = controlsToValidate.FirstOrDefault(Function(item) item.Tag = ec.PropertyName)
        If current IsNot Nothing Then
            ErrorProvider1.SetError(
                panel1.Controls.Find($"{ec.PropertyName}TextBox", False)(0),
                ec.ErrorMessage)
        End If
    Next
Else
    _bsContacts.Add(contact)
    If _dataGridViewSizeDone Then
        Return
    End If
 
    dataGridView1.ExpandColumns()
    _dataGridViewSizeDone = True
End If

Unusual rules

There can be special situations that just don't fit into the standard rules, for instance, a string field is limited to a word count rather than a length. Here a custom validation class can be created and used

Imports System.ComponentModel.DataAnnotations
 
Namespace Rules
 
    Public Class  MaxWordAttributes
        Inherits ValidationAttribute
 
        Private ReadOnly  mMaxWords As  Integer
 
        Public Sub  New(maxWords As Integer)
            MyBase.New("{0} has to many words.")
            mMaxWords = maxWords
        End Sub
        Protected Overrides  Function IsValid(
            value As  Object, validationContext As ValidationContext) As ValidationResult
 
            If value Is Nothing  Then
                Return ValidationResult.Success
            End If
 
            Dim textValue = value.ToString()
 
            If textValue.Split(" "c).Length <= mMaxWords Then
                Return ValidationResult.Success
            End If
 
            Dim errorMessage = FormatErrorMessage(validationContext.DisplayName)
            Return New  ValidationResult(errorMessage)
 
        End Function
    End Class
End Namespace

Usage where only eight words are permitted.

<MaxWordAttributes(8)>
Public Property  Words() As  String

 

How to implement in a project

  1. The the project Validator library from the source code repository and add it to your Visual Studio solution.
  2. In your project add a reference to the validation library.
  3. Use the appropriate attributes on properties of your class which need validation e.g.  <Required(ErrorMessage:="Your error message")> or custom attribute e.g. BirthDateValidation.  
  4. In a form create a class instance from data inputted by a user.
  5. Pass the class instance to the validator as per this example.

For a very easy to follow implementation for the above see the following sample project in the GitHub repository. 

Summary

In this article methods have been presented to validate information entered by users in Windows Form applications using Data Annotations which is usually done in web applications. What has not been shown is working with conventional DataSet, DataTable containers yet these methods presented may integrate into code interacting with DataSet and or DataTable containers but validating with a class then if all data validates add data in the class into a DataTable. Both basic and advance methods have been shown although they don’t cover all possible situations the foundation is here for developers to expand upon by examining and running various code samples provided in the accompanying source code.

When moving to ASP.NET the validator classes are not required, only your custom validation rules as validators are within ASP.NET MVC.

See also

Data Validation in MVVM C# 

References

Adding Validation to the model 
How to Validate Forms with ASP.NET MVC Data Annotations 

Source code

https://github.com/karenpayneoregon/ClassValidationVisualBasic