Perform custom validation in Blazor
Validating form data is one of the most common task in any web application. ASP.NET Core offers a set of validation attributes for performing model level data validations. Blazor apps can also make use of the same validation attributes. Some of the validation attributes include [Required], [StringLength], [Range], and [Compare]. At times you want to perform some validation not covered by these attributes. This is when you want to create a custom validation attribute. In this article we will discuss how that can be done.
Begin by creating a new Blazor Web Application using Visual Studio Code. Once the app gets created, add a new folder named Models under the project root.
Then add a new class called Employee in the Models folder. And write the following code in it :
public class Employee
{
[Required]
public int EmployeeID { get; set; }
[Required]
public string FullName { get; set; }
[Required]
public string Branch { get; set; }
[Required]
public string Department { get; set; }
}
As you can see, the Employee class consists of four properties namely EmployeeID, FullName, Branch, and Department. All the properties are decorated with [Required] attribute indicating that all of them must have some value assigned to them.
Now, let's assume that we want to perform this validation -- the company has branches all over the world as indicated by the Branch property. Each Branch has a few Departments. For example, US branch might have Sales and Marketing departments and India branch might have Manufacturing and Packaging departments. The Department assigned to a particular Employee must be belong to the Branch specified for that Employee.
This validation is not covered by any of the inbuilt validators and hence we must resort to some custom technique.
In order to accomplish our task we can create a custom validation attribute and use it on the Department property. A custom validation attribute inherits from the ValidationAttribute class. We can write our custom validation logic by overriding its IsValid() method.
Let's do just that.
Add a new class inside the Models folder called BranchDepartmentAttribute and inherit it from the ValidationAttribute class. The following code shows the skeleton of the BranchDepartmentAttribute class.
public class BranchDepartmentAttribute
: ValidationAttribute
{
protected override ValidationResult
IsValid (object value,
ValidationContext context )
{
}
}
Attribute class names usually have the Attribute suffix. So, the class name is BranchDepartmentAttribute but while using this attribute you will use just [BranchDepartment]. The BranchDepartmentAttribute class inherits from the ValidationAttribute class from the System.ComponentModel.DataAnnotations namespace.
Inside, there is IsValid() overridden method. The IsValid() method accepts two parameters -- current value of the property (Department property in this case) and the ValidationContext. It returns a ValidationResult object. If Isvalid() returns null it indicates that there are no validation errors. On the other hand if there are any validation errors, you should indicate that through a ValidationResult object.
For our illustration we will use a simple hard-coded branch and department values. But you can add database logic if that's your need. The following code shows the IsValid() method with a sample validation logic added to it.
protected override ValidationResult IsValid
(object value, ValidationContext context )
{
var obj = context.ObjectInstance as Employee;
var branch = obj.Branch;
string newValue = value.ToString().ToLower();
switch(branch)
{
case "US":
if(newValue=="sales" ||
newValue=="marketing")
return ValidationResult.Success;
break;
case "UK":
if(newValue=="rd" ||
newValue=="admin")
return null;
break;
case "IN":
if(newValue=="manufacturing" ||
newValue=="packaging")
return null;
break;
}
return new ValidationResult(ErrorMessage,
new[] { context.MemberName });
}
This code grabs a reference to the Employee object being validated using the ObjectInstance property of the ValidationContext parameter. The data type of ObjectInstance is object. So, we type convert it to Employee and read its Branch property. A series of switch blocks checks the Branch value and accordingly the Department value. Of course, you can change / improve this piece of code as per your requirements and logic.
If the Department belongs to a Branch, the code returns null indicating successful validation. At the end of the checking (if a Department doesn't belong to a Branch) the code returns a ValidationResult object by specifying an ErrorMessage and the MemberName.
Notice that in the first switch block the code uses ValidationResult.Success whereas in the other switch blocks it uses null. The ValidationResult.Success is a null value but for better readability you can use ValidationResult.Success instead of null. Here I have used both just to highlight this fact.
Our custom validation attribute is ready. Let's use it in the Employee class. Add the following code on top of the Department property.
public class Employee
{
[Required]
public int EmployeeID { get; set; }
[Required]
public string FullName { get; set; }
[Required]
public string Branch { get; set; }
[Required]
[BranchDepartment
(ErrorMessage = "Invalid department")]
public string Department { get; set; }
}
Notice the code shown in bold letters that indicates the usage of the [BranchDepartment] attribute. We specify a custom error message using the ErrorMessage property. The ErrorMessage property comes from the ValidationAttribute base class.
In order to see the [BranchDepartment] attribute in action we need a Blazor data entry form. So, open the Home.razor component from the Components -- Pages folder of the project and write the following markup and code in it.
@page "/"
@using CustomValidationInBlazorDemo.Models
@rendermode InteractiveServer
<h3>@Message</h3>
<EditForm Model="emp" OnValidSubmit="Save">
<DataAnnotationsValidator />
<label for="EmplyeeID">Employee ID :</label>
<InputNumber @bind-Value = "emp.EmployeeID" />
<ValidationMessage For="@(() => emp.EmployeeID)" />
<br />
<label for="FullName">Full Name :</label>
<InputText @bind-Value = "emp.FullName" />
<ValidationMessage For="@(() => emp.FullName)" />
<br />
<label for="Branch">Branch :</label>
<InputText @bind-Value = "emp.Branch" />
<ValidationMessage For="@(() => emp.Branch)" />
<br />
<label for="Department">Department :</label>
<InputText @bind-Value = "emp.Department" />
<ValidationMessage For="@(() => emp.Department)" />
<br />
<button type="submit">Save Data</button>
<br />
<ValidationSummary/>
</EditForm>
@code{
private Employee emp = new Employee();
private string Message;
public void Save()
{
Message = "Employee data saved!";
}
}
The markup shown above uses Blazor's EditForm and Input components to render a data entry form like this :
The EditForm has Employee model object as initialized in the @code block. A success message is shown using Message variable. The Save() method handles the form submission and simply sets the success message.
Notice that the EditForm uses DataAnnotationsValidator component to enable data annotations based validations for the form. The ValidationMessage and ValidationSummary components are used to display the validation errors if any. Other input components are quite simple and straightforward and hence I won't go into their details here.
Run the Blazor app and enter some wrong combination of the Branch and the Department. If you try to submit the form you should get an error message as shown below:
Now enter some correct combination such as US and Sales. This time you should see the success message.
Although our [BranchDepartment] attribute is working as expected, there is a limitation in its current implementation. It is tightly coupled with the Employee class because the IsValid() hard-codes the Employee class name :
var obj = context.ObjectInstance as Employee;
var branch = obj.Branch;
Wouldn't it be great if we can remove this tight coupling and make [BranchDepartment] more general purpose validation attribute?
Let's do that by modifying the IsValid() method slightly.
protected override ValidationResult IsValid
(object value, ValidationContext context )
{
PropertyInfo propInfo;
propInfo = context.ObjectType.
GetProperty("Branch");
if (propInfo == null)
{
return new ValidationResult
(ErrorMessage, new[]
{ context.MemberName });
}
var branch = propInfo.GetValue
(context.ObjectInstance, null);
// other code remains the same
}
This time we use reflection to read the Branch property of the underlying object. For that we use a PropertyInfo object retrieved using the GetProperty() method. If PropertyInfo is null it indicates that the Branch property is missing and hence further validation can't be performed. So, we return error message via ValidationResult. Otherwise, we retrieve the Branch value using the GetValue() method of the PropertyInfo object.
Now the [BranchDepartment] attribute can be used with any class. Run the application again and confirm its working as before.
That's it for now! Keep coding!!