In this article, we are going to learn how to use regular expressions for user input validation in Blazor WebAssembly applications.
Let’s get started.
Regular Expressions
Regular expressions (regex or regexp for short) are character sequences that we use to match patterns on strings. Every major programming language has a regex engine embedded in it and C#, of course, is no exception.
When working in a Blazor WebAssembly application, we can easily embed complex validations in our data model using regular expressions in combination with data annotations.
Example Project: Employee Registration Form
To test all our examples we are going to create a simple Blazor WebAssembly client application. It hosts a single page with a hypothetical employee registration form.
We will create a new Blazor WebAssembly project and add an EmployeeRegistration.razor
page and a model for our form in EmployeeRegistrationModel.cs
:
Basic Model With Data Annotations
Now, let’s add the EmployeeRegistrationModel
class. It contains properties matching each of the data fields in our form:
using System.ComponentModel.DataAnnotations; namespace UserInputValidationWithRegex.Models { public class EmployeeRegistrationModel { [Required] public string Name { get; set; } [Required] public string Address { get; set; } [Required] public string ZipCode { get; set; } [Required] public string SocialSecurityNumber { get; set; } [Required] public string Email { get; set; } [Required] public string Username { get; set; } [Required] public string Password { get; set; } [Required] public string PhoneNumber { get; set; } } }
First, we include the data annotations namespace. Then, we use the Required
attribute to make all fields mandatory. Yes, we will ask our users to fill out our form completely.
Next, our page component will be based on a standard EditForm
component, some input controls, and a couple of other validation related components:
@page "/" @using UserInputValidationWithRegex.Models <PageTitle>@pageTitle</PageTitle> <h1>@pageTitle</h1> <EditForm Model="@model" OnValidSubmit="@HandleValidSubmit"> <DataAnnotationsValidator /> <ValidationSummary /> <div class="mb-3 col-9"> <label for="name" class="form-label">Employee Name</label> <InputText id="name" class="form-control" @bind-Value="model.Name" aria-describedby="nameHelp" /> <div id="nameHelp" class="form-text">Employee's first, middle and last names.</div> </div> <div class="mb-3 col-9"> <label for="address" class="form-label">Address</label> <InputText id="address" class="form-control" @bind-Value="model.Address" aria-describedby="addressHelp" /> <div id="addressHelp" class="form-text">Employee's address.</div> </div> <div class="mb-3 col-5"> <label for="zipCode" class="form-label">Zip Code</label> <InputText id="zipCode" class="form-control" @bind-Value="model.ZipCode" aria-describedby="zipCodeHelp" /> <div id="zipCodeHelp" class="form-text">Zip Code.</div> </div> <div class="mb-3 col-5"> <label for="socialSecurityNumber" class="form-label">Social Security Number</label> <InputText id="socialSecurityNumber" class="form-control" @bind-Value="model.SocialSecurityNumber" aria-describedby="socialSecurityNumberHelp" /> <div id="socialSecurityNumberHelp" class="form-text">Employee's social security number.</div> </div> <div class="mb-3 col-9"> <label for="email" class="form-label">E-Mail</label> <InputText id="email" class="form-control" @bind-Value="model.Email" aria-describedby="emailHelp" /> <div id="emailHelp" class="form-text">We'll never share your email with anyone else.</div> </div> <div class="mb-3 col-9"> <label for="username" class="form-label">Username</label> <InputText id="username" class="form-control" @bind-Value="model.Username" aria-describedby="usernameHelp" /> <div id="usernameHelp" class="form-text">Choose a unique username.</div> </div> <div class="mb-3 col-9"> <label for="password" class="form-label">Password</label> <InputText type="password" id="password" class="form-control" @bind-Value="model.Password" aria-describedby="passwordHelp" /> <div id="passwordHelp" class="form-text">Choose a strong password.</div> </div> <div class="mb-3 col-9"> <label for="phone" class="form-label">Phone Number</label> <InputText id="phone" class="form-control" @bind-Value="model.PhoneNumber" aria-describedby="phoneHelp" /> <div id="phoneHelp" class="form-text">Main contact phone number.</div> </div> <button type="submit" class="btn btn-primary">Submit</button> </EditForm> @code { private string pageTitle = "Employee Registration Form"; private EmployeeRegistrationModel model = new(); private void HandleValidSubmit() { // TODO: Handle form data } }
Right after the page directive, we include our Models namespace so this page has access to our EmployeeFormModel
class.
The next important element is our EditForm
component, whose Model
attribute is set to a private field model
defined in the code section at the bottom of the page. Also, we will set the form’s OnValidSubmit
attribute to the HandleValidInput
private method defined in the code section as well.
We must add a ValidationSummary
component inside our form so all the validation messages are displayed on the screen. Also, a DataAnnotationsValidator
inspects our form’s model object and calls validation against data annotations.
Finally, we add a set of InputText
components and a simple submit button. We bind InputText
components to each of our model’s properties using the @bind-Value
directive.
At this point, we should be able to successfully run our project and enter data in our form. If you try to submit the form leaving some fields empty the Required
annotations we have already in place should trigger some validation error messages:
Perfect. But that’s not what we’re here for.
Validate Blazor WebAssembly Form Fields With Regex
Now let’s explore how we can use regular expressions to easily implement more complex validations.
Employee Name
We want all employee names in our database to be as authentic as possible. One step we can take towards that is to check that the data input in that field looks like a person’s name.
Considering only the English language, a typical person’s name will be composed of uppercase and lowercase letters, spaces, and symbols like periods .
, hyphens -
or apostrophes '
. On the other hand, a person’s name would never contain numeric characters or symbols like backslashes \
or asterisks *
. Finally, we consider that any given name must be a minimum of two characters long.
Let’s come up with a regular expression that checks all of the previous conditions and apply it to our form model using data annotations:
[Required] [RegularExpression(@"^[a-zA-Z\s.\-']{2,}$", ErrorMessage = "Employee name contains invalid characters.")] public string Name { get; set; }
Creating regular expressions is a deep topic and we won’t be explaining the basics. For now, we are good knowing that a-z
and A-Z
both match the ranges of characters that together comprehend all letters in the alphabet regardless of their case. At the same time, \s
matches spaces, .
periods and '
apostrophes. We must escape the hyphen \-
to avoid ambiguities with the range syntax. Finally, {2,}
is a quantifier and validates that the total length of the string is equaled to or exceeds two.
That’s it, we have implemented a relatively complex validation logic with a single line of code:
Address
Addresses usually contain a street number and name at the beginning. Following that, we will require to enter the city name and state separated by a period. These are some examples of valid addresses:
23839 Arroyo Park Rd. Dayton, Minnesota(MN) 365 Payson St. San Dimas, California(CA)
A new expression to match all that logic will be significantly more complex. However, its building blocks remain the same:
[Required] [RegularExpression(@"^[0-9]+\s[a-zA-Z\s\-']{2,}.\s?[a-zA-Z\s\(\),]{2,}$", ErrorMessage = "Wrong address format.")] public string Address { get; set; }
Here, we use a series of character matchers and quantifiers along with some fixed characters to define the different sections of our address format.
For instance, [0-9]+
will match a street number with one or more digits followed by a fixed space \s
. Next, the street name matcher [a-zA-Z\s\-']{2,}
accepts at least two letters followed by a period and an optional space .\s?
and so on.
Zip Code
Zipcodes in the United States come in two flavors: A basic zip code is just five digits. There are, as well, extended codes adding four extra digits that may or may not be separated from the first five by a hyphen.
According to that, all these would valid zipcodes:
12345 12345-6789 123456789
To build a regular expression that matches exactly our rules for zip codes we will use the alternate token |
. We will provide two expressions and get a positive match whenever the input string fits either one or the other:
[Required] [RegularExpression(@"^[0-9]{5}$|^[0-9]{5}-?[0-9]{4}$", ErrorMessage = "Invalid zipcode format")] public string ZipCode { get; set; }
Social Security Number
United States’ social security number format is 000-00-0000
. However, not all digits are valid in every position. For instance, 666
or 900
to 999
aren’t valid for the first segment and none of the segments can consist of only zeros.
This time, we use the negative lookahead assertion ?!
to ensure that a certain set of expressions do not match the pattern without consuming string positions itself:
[Required] [RegularExpression(@"^(?!(000|666|9))\d{3}-(?!00)\d{2}-(?!0000)\d{4}$", ErrorMessage = "Invalid SSN")] public string SocialSecurityNumber { get; set; }
Username and Password
Typically, a username input field would only allow a certain length and a specific subset of characters.
[Required] [RegularExpression(@"^[\w.\-]{2,18}$", ErrorMessage = "Invalid username.")] public string Username { get; set; }
Here, we simplify the expression by using a new matcher \w
that is equivalent to [a-zA-Z0-9_]
.
Also, we use a quantifier to limit the length of the string to eight-teen characters. However, it is probably better to use the MaxLength
data annotation attribute for that.
Passwords, on the other hand, usually require at least one occurrence of certain character types. We will require our new employee to provide a password that matches the following rules:
- At least one
- number (0-9)
- uppercase letter
- lowercase letter
- non-alpha-numeric character
- Password length is between 8 and 32 characters with no spaces
[Required] [RegularExpression(@"^(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[^\w\d\s:])([^\s]){8,32}$", ErrorMessage = "Password doesn't meet security rules.")] public string Password { get; set; }
Checking that there’s at least one occurrence of a certain type of character is the tricky part. However, we can achieve it using the positive lookahead assertion ?=
. It checks that a given pattern appears in a string without consuming string positions itself.
Validating email addresses can be rather complex. Anyway, we can go for something simple that does the job just fine:
[Required] [RegularExpression(@"^((?!\.)[\w-_.]*[^.])(@\w+)(\.\w+(\.\w+)?[^.\W])$", ErrorMessage = "Invalid email address.")] public string Email { get; set; }
Phone Number
Finally, let’s validate our employee’s phone number. Phone numbers in the united states consist of ten digits and may be preceded by the country code +1
. Additionally, some people like to insert spaces to separate groups of digits +1 123 456 7890
:
[Required] [RegularExpression(@"^([+]?\d{1,2}[-\s]?|)\d{3}[-\s]?\d{3}[-\s]?\d{4}$", ErrorMessage = "Invalid phone number.")] public string PhoneNumber { get; set; }
Conclusion
In this article, we have learned what regular expressions are and why they are useful for user input validation.
We have learned how to integrate regular expressions in our Blazor WebAssembly application using data annotation attributes and, finally, we have implemented various complex validations in an example form.