Introduction
At the core of every application, be it a web application, mobile or desktop application, data is entered, processed and displayed in a useful way. Now since data is essential, we can't just have our applications accepting any form of data on face-value, what if the entered data is simply nothing?
So how is data entered into applications from end users? you guessed it; Forms.
Now it's true that we can verify the data before it gets stored in the application's database, it makes more sense to check it before it gets sent to the server therefore reducing the amounts of trips made to our brave server; Show some love for the server ❤️
With form validations, we can check the data upon pressing Submit, with some validations specified on the data model the form is dealing with, we can identify breaches in the data before it gets sent over to the server. So, instead of submitting the form data, waiting until the server notices that Oh, this data looks unusable and it cannot be stored, then get the validation errors back from the server so that we can show the user that what they typed in, simply doesn't work, we can port over this task, to happen directly on the client.
Let's Get Started, Shall We?
In most websites, you'll have DTOs or Data Transfer Objects, each is responsible for a task, such as creating/editing/getting certain resource, if that's the case, you would apply data validations on the ones related to creating a resource, or requests such as Sign in/Sign up. However, the concept taught here, works on everything, with everything. So wether you're using Fluent Validations or Data Annotations, the changes are very minor.
For simplicity, I'll create a local data model called User, with enough properties to showcase how we can handle form validations in Blazor in a nice way.
💡 Info: The same methodology applies for Blazor WASM and Blazor Server, so feel free to use whatever project type you like.
Let's begin by looking at our data model that we'll be using throughout this post:
namespace FormValidationsDemo.Models;
public class User
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? Email { get; set; }
public string? PasswordConfirmation { get; set; }
public string? Password { get; set; }
}
Now I'll add some data annotations to the model, feel free to add your custom validations if you'd like, but I'll try keeping it simple yet almost realistic:
using System.ComponentModel.DataAnnotations;
namespace FormValidationsDemo.Models;
public class User
{
[Required(ErrorMessage = "Your first name is required")]
public string? FirstName { get; set; }
[Required(ErrorMessage = "Your last name is required")]
public string? LastName { get; set; }
[Required(ErrorMessage = "Your email is required")]
[EmailAddress(ErrorMessage = "Please enter a valid email address")]
public string? Email { get; set; }
[Required(ErrorMessage = "Your password is required")]
public string? Password { get; set; }
[Required(ErrorMessage = "The password confirmation is required")]
[Compare("Password", ErrorMessage = "Password and password confirmation don't match")]
public string? PasswordConfirmation { get; set; }
}
Keeping it simple, we could've gone crazier with MaxLength
, MinLength
, RegularExpression
and more, but that's sufficient.
If you didn't catch up, let me briefly explain to you
- We're ensuring all properties cannot be null.
- The email address has to look like an email address (i.e contains an @ symbol).
- Password confirmation must match the specified password.
Now that our data model is ready, we'll now build our form that should mockingly, sign up a new user.
I have some starter code inside Index.razor
, which creates a card in the center of the page, and creates an EditForm
<div class="h-screen flex justify-center items-center bg-gray-100">
<div class="form-card">
<h1 class="text-3xl font-bold mb-5">Sign Up</h1>
<EditForm Model="userModel" OnValidSubmit="MockUserSignUp">
</EditForm>
</div>
</div>
@code {
private User userModel = new();
private void MockUserSignUp()
{
Console.WriteLine("You have been signed up! I Promise ;-)");
}
}
💡 NOTE: I'm using Tailwind Css to style the page elements, but please don't worry about the classes being applied as the page styles isn't what we're here for.
I want to talk to you about this thing: EditForm
This is a special form that we use in Blazor instead of the typical <form>
element that's used almost everywhere, why you may ask? Well, let's answer this
The EditForm
is used in Blazor when we want to bind a form to a data model, check for validations, and do a certain action, either when just submitted, OR, when submitted with valid data.
This component automatically generates something known as an EditContext
, which is essentially a class, that's responsible for storing information regarding the ongoing editing process on the form, like the values being changed.
💡 NOTE: The
EditContext
can be created manually in case you wanted to add some custom logic, but we're not going to go down this rabbit hole in this post.
Now I'll be adding the form fields inside of EditForm
, again, don't worry about the Tailwind classes there:
<EditForm Model="userModel" OnValidSubmit="MockUserSignUp">
<div class="form-group">
<label class="field-label">First Name</label>
<InputText @bind-Value="userModel.FirstName" class="text-input" />
</div>
<div class="form-group">
<label class="field-label">Last Name</label>
<InputText @bind-Value="userModel.LastName" class="text-input" />
</div>
<div class="form-group">
<label class="field-label">Email</label>
<InputText type="email" @bind-Value="userModel.Email" class="text-input" />
</div>
<div class="form-group">
<label class="field-label">Password</label>
<InputText type="password" @bind-Value="userModel.Password" class="text-input" />
</div>
<div class="form-group">
<label class="field-label">Password Confirmation</label>
<InputText type="password" @bind-Value="userModel.PasswordConfirmation" class="text-input" />
</div>
<div class="form-group">
<button type="submit" class="primary-btn">Sign up</button>
</div>
</EditForm>
Now that's our form ready, and everything is bound to the userModel object that we created earlier, we're set to check for validations, but before we do, if you were to run the project, click Sign up, with all the fields empty, the form would still submit, as you can see in the following image:
It works, but it shouldn't, right? Well, let's fix it!
After the opening tag of the EditForm
component, add this line:
<DataAnnotationsValidator />
Rerun the project now, hit Sign up, and it won't work anymore. Why?
That line we added right here, will check on with the EditContext
and use a technique called Reflection to check wether the values of the form fields, which are bound to the object model, do NOT violate the rules specified for the data model (The data annotations attributes we added to our User class), if any single property fails the check, the form will not submit. Pay close attention to the fact that we're using the EditForm's OnValidSubmit
event, if we were to use the other event ,OnSubmit
, pressing the button would complete the submission even if the data is not valid.
💡 NOTE: If you were using Fluent Validations, you wouldn't use
<DataAnnotationsValidator />
, instead, it would be something like<FluentValidationsValidator />
, now please note I'm not sure about the exact name of the component, but you can reference the package's documentation for further details.
Now, below the line we just added, add another line to add another component:
<ValidationSummary class="text-red-600 font-light" />
Now submitting an empty form, would look like this:
As you can see, we're getting a summary of all the validation rules that we've violated. (Quite a lot to be honest).
You can try filling up some data that violets other rules like password and password confirmation mismatch so that you can see the validations we applied being displayed to the user.
Improving the overall look of the form
The validations summary we saw earlier, put bluntly, looks ugly as hell, like in comparison with the look of our form, it's just too horrible to stay like that. Enter <ValidationMessage/>
, this bad boy right here, is our ticket for making our form look dope.
This component only displays the validation message for a specific form field, instead of a whole summary.
To make use of this, below the <InputText>
component, add this line:
<div class="form-group">
<label class="field-label">First Name</label>
<InputText @bind-Value="userModel.FirstName" class="text-input" />
<ValidationMessage For="(() => userModel.FirstName)" class="text-red-600 font-light mb-2"/>
</div>
Using the For
parameter of this component, we're essentially telling it which property to display the validation message for, the rest is for styling purposes.
Now as a practice task, add the same component to the rest of the form fields, and make sure to point them each to its respective property.
Final Code Result
The EditForm
should contain the following code after doing your practice task:
<EditForm Model="userModel" OnValidSubmit="MockUserSignUp">
<DataAnnotationsValidator />
<div class="form-group">
<label class="field-label">First Name</label>
<InputText @bind-Value="userModel.FirstName" class="text-input" />
<ValidationMessage For="(() => userModel.FirstName)" class="text-red-600 font-light" />
</div>
<div class="form-group">
<label class="field-label">Last Name</label>
<InputText @bind-Value="userModel.LastName" class="text-input" />
<ValidationMessage For="(() => userModel.LastName)" class="text-red-600 font-light" />
</div>
<div class="form-group">
<label class="field-label">Email</label>
<InputText type="email" @bind-Value="userModel.Email" class="text-input" />
<ValidationMessage For="(() => userModel.Email)" class="text-red-600 font-light" />
</div>
<div class="form-group">
<label class="field-label">Password</label>
<InputText type="password" @bind-Value="userModel.Password" class="text-input" />
<ValidationMessage For="(() => userModel.Password)" class="text-red-600 font-light" />
</div>
<div class="form-group">
<label class="field-label">Password Confirmation</label>
<InputText type="password" @bind-Value="userModel.PasswordConfirmation" class="text-input" />
<ValidationMessage For="(() => userModel.PasswordConfirmation)" class="text-red-600 font-light" />
</div>
<div class="form-group">
<button class="primary-btn">Sign up</button>
</div>
</EditForm>
Now submitting the form, should look something like this:
That's so much more elegant and nice looking than the whole summary being thrown all in one place.
Conclusion
We've covered a lot during this article, initially we introduced why form validations is necessary and a must-do process, we then created our data model, applied data annotations attributes to it, then created our EditForm
component so that we could work with validations and see how the different components such as DataAnnotationsValidator
can affect the overall behavior of the form, along side the form events, OnSubmit
and OnValidSubmit
. Finally, we used both techniques for displaying the validation messages to our end user and we saw which one is clearly better. However, as long as you're invested in making a nice looking form, then rendering out individual validation messages just beneath their respective fields is obviously the better choice, but in case looks don't bother you, then the first technique we covered would be an easy go-to choice for your project.
I hope you learned something new at the end of this article, and thanks for baring with me lengthy times!
...
Top comments (2)
Is it possible to validate like in react after blur or something like that or just after submit? I am new to blazor so sorry for dumb question.
I personally haven't used react, so I'm sorry if my answer doesn't satisfy your question
In the context of client-side validations, there's no request being made to the server, so there's no delay to show the user a loading/blurred screen until the model gets validated. However if you plan on doing the validations on the server, you can use a boolean variable, something like
isMakingRequest
, if it's true, you can display a blurred overlay, once the request is processed you can invert the variable and display the error messages received from the server (Assuming you have wired the server to return a proper list of validation errors as a response), but that would make the process slower as the request will have to visit the server first, instead of doing the whole thing on the client before making any requests