Common Validation Rules in LightSwitch Business Applications

Checking the validity of data input is a common requirement for any application that interacts with humans (and other systems), particularly business applications. I’ve never seen or written a data-entry application without implementing common validation rules for inputting data. LightSwitch has many ways to implement validation declaratively through the data designer as well as supporting custom code you write. Field (or property) validation is just one aspect of writing business rules in LightSwitch, but it’s definitely an important one. It’s your “first line of defense” in keeping user entered data consistent.

Although LightSwitch has some built-in business types and declarative validation settings, many times we need to write small snippets of code to check the format of data entered on a screen. Common things in the USA are States, ZIP codes, Social Security Numbers, UPIN, ISBN, etc. But you may also want to prevent things like numeric and symbolic values in people’s or place’s names or a variety of other rules based on string manipulation.

In this post, I’ll show you how to define declarative rules as well as write custom validation code in LightSwitch. I’ll also show you some common patterns you can use to validate strings. Let’s get started!

Declarative Validation Rules

First let’s walk through the types of validation you can specify declaratively without having to write any code. Let’s take a customer entity that we’ve designed with the data designer. It has the following fields:

image

Required Fields & Business Types

The first step to validating these fields is to determine their types and which ones are required. LightSwitch will automatically handle required fields as well as validation that comes with business types so you can set these up declaratively using the data designer without having to write any code. The built-in business types are Email, Phone, Money and Image but this is an extensibility point so you can download more. On our customer entity notice I’m only requiring that LastName is filled out. Required fields prevent the user from saving data if the field is left blank and the labels show up bolded on the screen.

image

When any validation fails, a message is displayed when the user’s cursor is in the field or if they click the validation summary at the top of the screen.

image

Notice that I have also selected the “Phone Number” and “Email Address” business types for the Phone and Email fields on our customer entity. Business types come with built-in validation and have additional properties that you can set in the properties window to indicate how the validation should work. For Email Address, you can select whether you want to provide a default email domain and if the domain is required:

image

For Phone Number you can supply additional formats it should validate against and in what order:

image

Specifying Field Maximum Lengths

Another important declarative validation rule is specifying a maximum length. By default all string fields default to 255 characters. This works for most of our fields because they have variable lengths and 255 will be plenty of room for our data. However, the SSN, State, and ZIP fields are fixed lengths of 11, 2 and 10 characters respectively. Anything over that isn’t a valid piece of data. You specify maximum lengths in the properties window at the bottom in the Validation section:

image

Preventing Duplicates with a Unique Index

You can also prevent duplicates by including required fields in a unique index. For instance if I wanted to prevent duplicate Last Names in the system then I could include it in the unique index.

image

Note that this adds the field to a unique index for the table and is enforced by the database. So the validation message will appear after the data is saved and checked on the server side. In the case of my customer entity this is not a good idea to limit duplicates this way because it’s pretty common to have customers with the same last name. You might consider using SSN but then we’d have to make this a required field and only customers with an SSN would be allowed into the system. This is too restrictive in this case, but including fields in a unique index can work well for other types of data entities where you want to prevent duplicate records.

Custom Validation Rules

When you can’t express a validation rule declaratively then you need to write some code. To write custom validation code, select the property you want on the entity then drop down the “Write Code” button and select the property_ Validate method:

image

This will open the code editor to a method stub that allows you to write the custom validation code. When a rule fails, you use the results object to add your error message.

 Private Sub LastName_Validate(results As EntityValidationResultsBuilder)
    ' Check the rule, if it fails then display an error message on the field:
    results.AddPropertyError("<Error Message>")
End Sub

You can also specify warnings and informational messages as well. Only error messages prevent data from being saved. To specify a warning or informational message on a field use the AddPropertyResult method and pass it the level of severity:

image

You can also specify entity-level errors which aren’t specific to certain fields, instead they will only show up in the validation summary at the top of the screen. You use the AddEntityError and AddEntityResult methods for this. These property validation methods run on the client first and then again on the server, however you just write them once and LightSwitch will take care of calling them at the appropriate times. For a deeper understanding of the validation framework please read: Overview of Data Validation in LightSwitch Applications

Simple String Validation & Formatting

The first rule I want to enforce is that the State field should always be entered in uppercase. There are a lot of string methods you can use to manipulate and validate string data. For instance to format strings in a variety of ways you can use the String.Format() method. There are also methods to find characters at certain positions (IndexOf), to check if a string contains another string (Contains), to return parts of strings (Substring), to trim whitespace (Trim), and much much more. See the documentation for all the string methods available to you.

In order to format the State field we can simply use the ToUpper() method. Since State isn’t a required field, we first check to see if the State property has a value and if so we make it upper case.

 Private Sub State_Validate(results As EntityValidationResultsBuilder)
    If Me.State <> "" Then
        Me.State = Me.State.ToUpper
    End If
End Sub

Notice that this doesn’t report a validation error to the user, it just formats the string they enter. You can also use the property_ Validate methods to perform formatting as well because the _Validate method will fire when the user tabs out of the field when the method runs on the client. However we still need to validate whether they entered a valid State code – “AA” is currently allowed and this isn’t a valid U.S. State. In order to validate all the possible state code combinations we can use Regular Expressions.

Using Regular Expressions

Regular expressions are used by many text editors, utilities, and programming languages to search and manipulate text based on patterns. Regular expressions are used all over the web to validate user input. In fact, one of my favorite sites is www.RegExLib.com which has thousands of community submitted patterns you can use in your own validation rules. In order to use regular expressions in your _Validate methods you use the RegEx class located in the System.Text.RegularExpressions namespave. So in order to check that the State is a valid US State we can write the following in bold:

 Imports System.Text.RegularExpressions

Namespace LightSwitchApplication
    Public Class Customer

        Private Sub State_Validate(results As EntityValidationResultsBuilder)
            If Me.State <> "" Then
                Me.State = Me.State.ToUpper


                Dim pattern = "^(?:(A[KLRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|" +                              "LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|" +                              "T[NX]|UT|V[AIT]|W[AIVY]))$"
                                If Not Regex.IsMatch(Me.State, pattern) Then                    results.AddPropertyError("Please enter a valid US State.")                End If

End If
End Sub
End Class
End Namespace

Similarly we can use regular expressions to check ZIP codes as well. However I want to allow both 5 digit and 9 digit ZIP codes. I also want to allow the user to not have to specify the dash so we’ll do a little formatting as well first.

 Private Sub ZIP_Validate(results As EntityValidationResultsBuilder)
    If Me.ZIP <> "" Then
        'Add the dash if the user didn't enter it and the ZIP code is 9 characters
        If Not Me.ZIP.Contains("-") AndAlso Me.ZIP.Length = 9 Then
            Me.ZIP = Me.ZIP.Substring(0, 5) + "-" + Me.ZIP.Substring(5)
        End If
        'Now validate based on regular expression pattern
        If Not Regex.IsMatch(Me.ZIP, "^\d{5}$|^\d{5}-\d{4}$") Then
             results.AddPropertyError("Please enter a valid US ZIP code.")
        End If
    End If
End Sub

Another rule I want to enforce is the SSN format that has the pattern “3 digits (dash) 2 digits (dash) 4 digits”. I want to do the same type of thing we did above where we won’t require the user to enter the dashes. So we can write the following:

 Private Sub SSN_Validate(results As EntityValidationResultsBuilder)
    If Me.SSN <> "" Then
        'Add the dashes if the user didn't enter it and the SSN is 9 characters
        If Not Me.SSN.Contains("-") AndAlso Me.SSN.Length = 9 Then
            Me.SSN = Me.SSN.Substring(0, 3) + "-" + Me.SSN.Substring(3, 2) + "-" + Me.SSN.Substring(5)
        End If

        'Now validate based on regular expression pattern
        If Not Regex.IsMatch(Me.SSN, "^\d{3}-\d{2}-\d{4}$") Then
            results.AddPropertyError("Please enter a valid SSN (i.e. 123-45-6789).")
        End If
    End If
End Sub

You can do a lot with regular expressions and string manipulation. The last rule I want to enforce is not allowing users to enter numbers or symbols in the LastName and FirstName fields. They should only contain alphabetical characters and spaces are allowed. We can do something like this to enforce that:

 Private Sub LastName_Validate(results As EntityValidationResultsBuilder)
    If Me.LastName <> "" Then
        'This pattern only allows letters and spaces
        If Not Regex.IsMatch(Me.LastName, "^[a-zA-Z\s]+$") Then
            results.AddPropertyError("Last Name can only contain alphabetical characters.")
        End If
    End If
End Sub

Private Sub FirstName_Validate(results As EntityValidationResultsBuilder)
    If Me.FirstName <> "" Then
        'This pattern only allows letters and spaces
        If Not Regex.IsMatch(Me.LastName, "^[a-zA-Z\s]+$") Then
            results.AddPropertyError("First Name can only contain alphabetical characters.")
        End If
    End If
End Sub

Notice that in this last example I’m using the same pattern. You’ll most likely have fields across entities in your application where you’ll want to use the same validation checks. When you start copying and duplicating code you should stop and think about consolidating it into a single library or class that you can call. This way if you have a bug in your validation code you fix it in just one place. Remember, the less code you write the less bugs you’ll have. ;-)

Creating a Common Validation Module

Let’s create a validation module that we can call from our validation methods that encapsulates all the types of validation routines we’d want to support across all the entities in our application. In the Solution Explorer flip to “File View” and under the Common project expand the UserCode folder. Right-click and add a new Module. I’ll name it CommonValidation.

image

To make these validation rules easy to discover and call from our _Validate methods we’ll create them as Extension Methods. Basically extensions methods “extend” types, like a string or integer or any other object type with your own custom methods. What’s cool is they appear in IntelliSense with you type the dot “.” after the type. To place extension methods in our module we just need to import the  System.Runtime.CompilerServices and then attribute our method with the <Extension()> attribute.

Let’s take a simple example before we move our complex validation code in here. For instance let’s create an extension method that extends a string type with a method called “MyMethod”. The first parameter to your extension method is the type you are extending. Here’s how we could write the module. (Notice I added the comment section by typing three single quotes (‘’’) above the <Extension>):

 Imports System.Runtime.CompilerServices

Module CommonValidation
    ''' <summary>
    ''' This is my extension method that does nothing at the moment. ;-)
    ''' </summary>
    ''' <param name="value">extend the string type</param>
    ''' <remarks></remarks>
    <Extension()>
    Public Sub MyMethod(ByVal value As String)
        'Do something
    End Sub
End Module

If we flip back to one of the property_ Validate methods then we will now see this extension method in IntelliSense on any string type. (You need to flip to the “All” tab).

image

Notice there are a lot of “built-in” extension methods in the .NET framework once you flip to the “All” tab in IntelliSense. So if you were wondering why the icons looked different now you know why :-). Extension methods are a great way to add additional functionality to a type without having to change the implementation of the actual class.

Now sometimes we’ll need to pass parameters and/or return a value from our extension method. Any parameters you specify after the first one in an extension method becomes a parameter the caller needs to pass. You can also return values from extension methods by declaring them as a Function instead.

 Imports System.Runtime.CompilerServices

Module CommonValidation
    ''' <summary>
    ''' This is my extension method that still does nothing. :-)
    ''' </summary>
    ''' <param name="value">extend the string type</param>
    ''' <param name="param">demoing parameter</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()>
    Public Function MyMethod(ByVal value As String, param As Boolean) As Boolean
        'Do something
        Return True
    End Function
End Module

If we use this extension method now, you’ll see that a boolean parameter is required.

image

Now let’s start moving our validation methods in here. Let’s start with the State validation method. Notice in this method that we first format the value by making it upper case. In order to change the value of the type we’re extending we just pass the parameter ByRef instead of ByVal. I also want to allow passing of a parameter that indicates whether the field can be empty. So now here is our CommonValidation module with an IsState extension method:

 Imports System.Runtime.CompilerServices
Imports System.Text.RegularExpressions

Module CommonValidation
    ''' <summary>
    ''' Checks if the string is formatted as a 2 character US state code
    ''' </summary>
    ''' <param name="state">string type extension</param>
    ''' <param name="isEmptyOK">True if empty values are allowed, otherwise false</param>
    ''' <returns>True if the string is a valid US state, otherwise false</returns>
    ''' <remarks></remarks>
    <Extension()>
    Public Function IsState(ByRef state As String, ByVal isEmptyOK As Boolean) As Boolean
        If state <> "" Then
            'States should always be upper case
            state = state.ToUpper

            'Now validate based on regular expression pattern
            Dim pattern = "^(?:(A[KLRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|" +
                          "LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|" +
                          "T[NX]|UT|V[AIT]|W[AIVY]))$"

            Return Regex.IsMatch(state, pattern)
        Else
            Return isEmptyOK
        End If
    End Function
End Module

Notice the nice IntelliSense we now get back in the _Validate methods when we call our extension method:

image

To finish off the State_Validate method all we need to do is check the return value to determine if we should add the property error or not:

 Private Sub State_Validate(results As EntityValidationResultsBuilder)
    If Not Me.State.IsState(True) Then
        results.AddPropertyError("Please enter a valid US State.")
    End If
End Sub

OK cool! So here are all of the extension validation methods in the module:

 Imports System.Runtime.CompilerServices
Imports System.Text.RegularExpressions

Module CommonValidation
    ''' <summary>
    ''' Checks if the string is formatted as a 2 character US state code
    ''' </summary>
    ''' <param name="state">string type extension</param>
    ''' <param name="isEmptyOK">True if empty values are allowed, otherwise false</param>
    ''' <returns>True if the string is a valid US state, otherwise false</returns>
    ''' <remarks></remarks>
    <Extension()>
    Public Function IsState(ByRef state As String, ByVal isEmptyOK As Boolean) As Boolean
        If state <> "" Then
            'States should always be upper case
            state = state.ToUpper

            'Now validate based on regular expression pattern
            Dim pattern = "^(?:(A[KLRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|" +
                          "LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|" +
                          "T[NX]|UT|V[AIT]|W[AIVY]))$"

            Return Regex.IsMatch(state, pattern)
        Else
            Return isEmptyOK
        End If
    End Function
    ''' <summary>
    ''' Checks if the string is formatted as a valid US ZIP code
    ''' </summary>
    ''' <param name="zip">string type extension</param>
    ''' <param name="isEmptyOK">True if empty values are allowed, otherwise false</param>
    ''' <returns>True if the string is a valid ZIP code, otherwise false</returns>
    ''' <remarks></remarks>
    <Extension()>
    Public Function IsZIP(ByRef zip As String, ByVal isEmptyOK As Boolean) As Boolean
        If zip <> "" Then
            'Add the dash if the user didn't enter it and the ZIP code is 9 characters
            If Not zip.Contains("-") AndAlso zip.Length = 9 Then
                zip = zip.Substring(0, 5) + "-" + zip.Substring(5)
            End If
            'Now validate based on regular expression pattern
            Return Regex.IsMatch(zip, "^\d{5}$|^\d{5}-\d{4}$")
        Else
            Return isEmptyOK
        End If
    End Function
    ''' <summary>
    ''' Checks if the string is formatted as a Social Security Number
    ''' </summary>
    ''' <param name="ssn">string type extension</param>
    ''' <param name="isEmptyOK">True if empty values are allowed, otherwise false</param>
    ''' <returns>True if the string is a valid SSN, otherwise false</returns>
    ''' <remarks></remarks>
    <Extension()>
    Public Function IsSSN(ByRef ssn As String, ByVal isEmptyOK As Boolean) As Boolean
        If ssn <> "" Then
            'Add the dashes if the user didn't enter it and the SSN is 9 characters
            If Not ssn.Contains("-") AndAlso ssn.Length = 9 Then
                ssn = ssn.Substring(0, 3) + "-" + ssn.Substring(3, 2) + "-" + ssn.Substring(5)
            End If

            'Now validate based on regular expression pattern
            Return Regex.IsMatch(ssn, "^\d{3}-\d{2}-\d{4}$")
        Else
            Return isEmptyOK
        End If
    End Function

    ''' <summary>
    ''' Checks if the string contains only upper and lower case letters
    ''' </summary>
    ''' <param name="value">string type extension</param>
    ''' <param name="isEmptyOK">True if empty values are allowed, otherwise false</param>
    ''' <param name="isWhitespaceOK">True if spaces are allowed, otherwise false</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()>
    Public Function IsAlpha(ByVal value As String,
                            ByVal isEmptyOK As Boolean,
                            ByVal isWhitespaceOK As Boolean) As Boolean
        If value <> "" Then
            'Validation for strings that must be Alphabetical characters only. 
            Dim pattern As String
            If isWhitespaceOK Then
                'Allows spaces 
                pattern = "^[a-zA-Z\s]+$"
            Else
                'No spaces
                pattern = "^[a-zA-Z]+$"
            End If

            Return Regex.IsMatch(value, pattern)
        Else
            Return isEmptyOK
        End If
    End Function

    ''' <summary>
    ''' Checks if the string contains only upper and lower case letters and/or numbers
    ''' </summary>
    ''' <param name="value">string type extension</param>
    ''' <param name="isEmptyOK">True if empty values are allowed, otherwise false</param>
    ''' <param name="isWhitespaceOK">True if spaces are allowed, otherwise false</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()>
    Public Function IsAlphaNumeric(ByVal value As String,
                                   ByVal isEmptyOK As Boolean,
                                   ByVal isWhitespaceOK As Boolean) As Boolean
        If value <> "" Then
            'Validation for strings that must be AlphaNumeric characters only. 
            Dim pattern As String
            If isWhitespaceOK Then
                'Allows spaces 
                pattern = "^[a-zA-Z0-9\s]+$"
            Else
                'No spaces
                pattern = "^[a-zA-Z0-9]+$"
            End If

            Return Regex.IsMatch(value, pattern)
        Else
            Return isEmptyOK
        End If
    End Function
End Module

And finally, our customer entity that calls these methods. Notice how much cleaner the code is now and we can reuse these across any entity in the application.

 Namespace LightSwitchApplication
    Public Class Customer

        Private Sub State_Validate(results As EntityValidationResultsBuilder)
            If Not Me.State.IsState(True) Then
                results.AddPropertyError("Please enter a valid US State.")
            End If
        End Sub

        Private Sub SSN_Validate(results As EntityValidationResultsBuilder)
            If Not Me.SSN.IsSSN(True) Then
                results.AddPropertyError("Please enter a valid SSN (i.e. 123-45-6789).")
            End If
        End Sub

        Private Sub ZIP_Validate(results As EntityValidationResultsBuilder)
            If Not Me.ZIP.IsZIP(True) Then
                results.AddPropertyError("Please enter a valid US ZIP code.")
            End If
        End Sub

        Private Sub LastName_Validate(results As EntityValidationResultsBuilder)
            If Not Me.LastName.IsAlpha(False, True) Then
                results.AddPropertyError("Last Name can only contain alphabetical characters.")
            End If
        End Sub

        Private Sub FirstName_Validate(results As EntityValidationResultsBuilder)
            If Not Me.FirstName.IsAlpha(True, True) Then
                results.AddPropertyError("First Name can only contain alphabetical characters.")
            End If
        End Sub
    End Class
End Namespace

To pull this all together into a concrete example I’ve included the sample code here:

https://code.msdn.microsoft.com/Common-Validation-Rules-in-397bf46b

I hope this helps get you started writing your own more complex business rules and validation methods for your LightSwitch applications.

Enjoy!