In a form to validate values against data store or database in a blazor server application, we can achieve this by creating a custom blazor component that can be called a Custom Validator Component. Here our Validator Component is not a built component, here we make a plain class as a component by inheriting 'Microsoft.AspNetCore.Components.ComponentBase', and then we use built-in blazor form components and make it as Custom Validator Component. Our Custom Validator Components mainly built on form components like 'Microsoft.AspNetCore.Components.Forms.ValidationMessageStore' and 'Microsoft.AspNetCore.Components.Forms.EditContext'.
Now to fix this issue we need to clear old messages from 'ValidationMessageStore'. So we need to update the code in our 'CustomValidator' component below.
Create A Blazor Server Application:
Let's understand our topic more in-depth by making a sample on it, so let's create a Blazor Server sample application. Here my sample targeted framework is '.Net5'.Most recommended IDE's for development in Visual Studio 2019(Version 16.8.* that supports .Net5) or Visual Studio Code.
Create A Fake Store Data Logic:
In real applications create DbContext to fetch data from the database and validate the values of form data against the database values. Here for our sample instead of the database, I'm going to use fake data.
So let's create the business logic file in which we will check the email entered by the user in our form is already exists in our fake data collection.
Logics/TestLogic.cs:
using System.Collections.Generic; using System.Linq; namespace BS.Forms.ValidatorComponent.Logics { public class TestLogic : ITestLogic { public List<string> emails = new List<string> { "naveen@gmail.com", "hemanth@gmail.com", "ram@gmail.com" }; public bool IsEmailExist(string newEmail) { return emails.Any(_ => _.ToLower() == newEmail.ToLower()); } } }
- Here I have created a collection of fake emails. Also, created a method to check the email entered in the form already in my collection of fake emails
namespace BS.Forms.ValidatorComponent.Logics { public interface ITestLogic { bool IsEmailExist(string newEmail); } }Now imports its namespace in '_Imports.razor'
_Imports.razor:
@using BS.Forms.ValidatorComponent.LogicsNow register in the Startup file.
Startup.cs:
services.AddSingleton<ITestLogic, TestLogic>();
Validator Component:
Now let's create our validator component, that can be used to update the error messages on validating the form fields against the server data.
Components/CustomValidator.cs:
using System; using System.Collections.Generic; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Forms; namespace BS.Forms.ValidatorComponent.Components { public class CustomValidator : ComponentBase { private ValidationMessageStore messageStore; [CascadingParameter] public EditContext CurrentEditContext { get; set; } protected override void OnInitialized() { if (CurrentEditContext == null) { throw new InvalidOperationException("To use validator component your razor page should have the edit component"); } messageStore = new ValidationMessageStore(CurrentEditContext); } public void DisplayErrors(Dictionary<string, List<string>> errors) { foreach (var error in errors) { messageStore.Add(CurrentEditContext.Field(error.Key),error.Value); } CurrentEditContext.NotifyValidationStateChanged(); } } }
- (Line: 9) The 'CustomValidator' is a POCO class that we made as a component by inheriting 'Microsoft.AspNetCore.Components.ComponentBase'.
- (Line: 11) Declared a variable of type 'Microsoft.AspNetCore.Components.Forms.ValidationMessageStore' that will be used to store error message of the form.
- (Line: 12&13) Declared cascading property of type 'Microsoft.AspNetCore.Components.Forms.EditContext' which means our 'CustomValidator' component should be rendered as a child component to 'EditForm'(built-in blazor component) so that it will have the access to cascading property of 'EditContext'.
- (Line: 15-22) Overrides the Blazor life cycle method 'OnInitialized'.
- (Line: 17-20) Throws an invalid operation exception if we don't have the 'EditContext' property that means we are trying to render our 'CustomValidator' outside of the 'EditForm'(built-in blazor component).
- (Line: 21) Initializing the 'ValidationMessageStore' by taking 'EditContext' as the input parameter.
- (Line: 24-31) Method to add our custom message to the 'ValidationMessage' variable, here it takes dictionary collection of error records to add into the 'ValidationMessage'.
- (Line: 28) While adding an error message to 'ValidationMessageStore' we are fetching 'FieldIdentifier' of EditForm so that while displaying the error on UI we can able to show the error for the appropriate form field.
- (Line: 30) After adding errors then we need to notify that form our state has been changed so that new errors can be displayed or rendered on our UI
_Import.razor:
@using BS.Forms.ValidatorComponent.Components
Create Form Model:
Now let's create a model for our blazor 'EditForm'.
Models/User.cs:
using System.ComponentModel.DataAnnotations; namespace BS.Forms.ValidatorComponent.Models { public class User { public string Email { get; set; } } }
- Created a simple user model that contains an 'Email' property.
_Import.razor:
@using BS.Forms.ValidatorComponent.Models
Add Blazor Form:
Now let's create a sample blazor form that contains an input field for email on user clicks on the submit button then we need to validate the email value already exists in our data store or not. If the email entered by the user already exists then we need to display the error message to the user.
Now update our Index.razor as follows.
Pages/Index.razor:(Html Part)
@page "/" @inject ITestLogic _testLogic; <div> <EditForm Model="@user" OnValidSubmit="@HandleValidSubmit"> <DataAnnotationsValidator></DataAnnotationsValidator> <ValidationSummary></ValidationSummary> <CustomValidator @ref="customValidator"></CustomValidator> <div class="form-group row"> <label for="txtEmail" col="col-sm-2 col-form-label">Email</label> <div class="col-sm-10"> <InputText id="txtEmail" @bind-Value="user.Email" /> </div> </div> <div class="form-group row"> <button class="btn btn-primary" type="submit">Submit</button> </div> </EditForm> </div>
- (Line: 2) Injected our 'ITestLogic'.
- (Line: 4) Rendered blazor 'EditForm'. Here assigned our 'user' object to 'Model' property. Also registered an event 'OnValidSubmit' with a callback method.
- (Line: 5) The 'DataAnnotationValidator' enables form validation which is a built-in blazor component.
- (Line: 6) The 'ValidationSummary' is a built-in Balzer component that will render all error messages of the form.
- (Line: 7) The 'CustomValidator' is our custom blazor component to validate form values at the server against the data store and capture those errors into it. Here we '@ref' to capture the component element and handle it by the variable assigned to it.
- (Line: 11) Email input field rendered.
@code{ private CustomValidator customValidator; private User user = new User(); private void HandleValidSubmit() { var errors = new Dictionary<string, List<string>>(); if (_testLogic.IsEmailExist(user.Email)) { errors.Add(nameof(user.Email), new List<string> { "Email already registered" }); } if (errors.Count > 0) { customValidator.DisplayErrors(errors); } } }
- (Line: 2) Declare CustomValidatory type variable, this variable used to refer to our CustomValidator component render in the Html.
- (Line: 3) The 'User' model initialized and assigned to our EditForm Model property.
- The 'HandleValidSubmit' method registered the callback method to the 'OnValidSubmit' event. The 'OnValidSubmit' only invokes when our form is valid. But here we are not using any DataAnotation validation properties, so here my form is always valid, so on clicking the submit button 'HandleValidSubmit' gets invoked.
- Inside the HandleValidSubmit method, we are creating a dictionary to capture the errors. Here we are validating our input field email value against our collection of fake emails. If the email already exists then we are adding an error message to the dictionary. Finally, if we have at least one error message we are passing all our errors to the 'DisplayError' method in our 'CustomValidator' component.
Clear Error Message On Raise Of OnValidationRequested Form Event:
We are storing our CustomValidator component error messages in 'ValidationMessageStore'. We need to remove those messages appropriately when required.
First, let's observe the problem when we don't clear the error message. So let's enter an email that already exists in our data store then we will see the error as below
Now let's enter an email that does not exist on our server. But still, we will see the old message as follows.First, let's observe the problem when we don't clear the error message. So let's enter an email that already exists in our data store then we will see the error as below
Now to fix this issue we need to clear old messages from 'ValidationMessageStore'. So we need to update the code in our 'CustomValidator' component below.
Components/CustomValidator.cs:(Update In OnInitialized Method)
protected override void OnInitialized() { if (CurrentEditContext == null) { throw new InvalidOperationException("To use validator component your razor page should have the edit component"); } messageStore = new ValidationMessageStore(CurrentEditContext); CurrentEditContext.OnValidationRequested += (s, e) => messageStore.Clear(); }
- (Line: 8) Here we added a new line like registering the 'OnValidationRequested' event. On invoking this event we are clearing our error messages from the 'ValidationMessageStore'. So this 'OnValidationRequested' event raised when we clicked our submit button 'EditFrom'.
Clear Error Message On Input Field Change Event:
We can also remove the error message on the input field change event if we want for that we need to update our CustomValidator component logic for that as below.
Components/CustomValidator.cs:(Update In OnInitialized Method)
protected override void OnInitialized() { if (CurrentEditContext == null) { throw new InvalidOperationException("To use validator component your razor page should have the edit component"); } messageStore = new ValidationMessageStore(CurrentEditContext); CurrentEditContext.OnValidationRequested += (s, e) => messageStore.Clear(); CurrentEditContext.OnFieldChanged += (s, e) => messageStore.Clear(e.FieldIdentifier); }
- (Line: 9) Here we registered the 'OnFieldChaged' event so this line will be executed after changing any field inside of the form. But here we are not clearing all messages, we only clearing the message related to the form field that was changed by using its name, because while saving the error message we have written our logic to save the error message in the dictionary object with the field name as the key.
Support Me!
Buy Me A Coffee
PayPal Me
Wrapping Up:
Hopefully, I think this article delivered some useful information on Custom Validator Component in Blazor Server Application. I love to have your feedback, suggestions, and better techniques in the comment section below.
Should the events also be removed to prevent memory leaks? Using IDisposable or something similar?
ReplyDeleteVerry creative post
ReplyDelete