blazor-devexpress-dot-blazor-dot-dxgrid-d56e3317.md
Allows you to declare custom validator components.
Namespace : DevExpress.Blazor
Assembly : DevExpress.Blazor.v25.2.dll
NuGet Package : DevExpress.Blazor
[Parameter]
public RenderFragment CustomValidators { get; set; }
| Type | Description |
|---|---|
| RenderFragment |
A template (render fragment) that allows you to declare custom validators.
|
The Grid uses the DataAnnotationsValidator to validate user input based on data annotation attributes defined in an edit model.
You can also create custom validator components as described in the following Microsoft topic: Validator components. To enable custom validation in the Grid, declare validator components in the CustomValidators template.
Note that the declared custom validators override the standard DataAnnotationsValidator. If you need to use DataAnnotationsValidator in addition to the custom validators, declare it in the CustomValidators template explicitly.
For additional information about validation in the Grid, refer to the following topic: Validate User Input.
The following code snippet uses the custom validator to check the Title field value.
The following example uses a custom validator to evaluate the Title field value.
View Example: Custom Validation View Example: Display an error message from the Web API Service
@page "/"
@using CustomValidation.Models
@using Microsoft.EntityFrameworkCore
@inject IDbContextFactory<NorthwindContext> NorthwindContextFactory
@implements IDisposable
<DxGrid Data="GridDataSource"
EditModelSaving="OnEditModelSaving"
CustomizeDataRowEditor="OnCustomizeDataRowEditor"
CustomizeEditModel="OnCustomizeEditModel"
KeyFieldName="EmployeeId">
<Columns>
<DxGridCommandColumn DeleteButtonVisible="false" />
<DxGridDataColumn FieldName="FirstName" />
<DxGridDataColumn FieldName="LastName" />
<DxGridDataColumn FieldName="Title" />
<DxGridDataColumn FieldName="HireDate" />
</Columns>
<EditFormTemplate Context="editFormContext">
<DxFormLayout>
<DxFormLayoutItem Caption="First Name:">
@editFormContext.GetEditor("FirstName")
</DxFormLayoutItem>
<DxFormLayoutItem Caption="Last Name:">
@editFormContext.GetEditor("LastName")
</DxFormLayoutItem>
<DxFormLayoutItem Caption="Title:">
@editFormContext.GetEditor("Title")
</DxFormLayoutItem>
<DxFormLayoutItem Caption="Hire Date:">
@editFormContext.GetEditor("HireDate")
</DxFormLayoutItem>
</DxFormLayout>
</EditFormTemplate>
<CustomValidators>
<MyCustomValidator DataItemValidating="ValidateGridData" />
</CustomValidators>
</DxGrid>
@code {
IEnumerable<object> GridDataSource { get; set; }
NorthwindContext Northwind { get; set; }
void ValidateGridData(ValidationMessageStoreEventArgs e) {
var employee = (Employee)e.EditModel;
if (employee.Title == null || !employee.Title.Contains("Sales")) {
e.AddError(nameof(employee.Title), "The Title field value should contain 'Sales'.");
}
}
protected override async Task OnInitializedAsync() {
Northwind = NorthwindContextFactory.CreateDbContext();
GridDataSource = await Northwind.Employees.ToListAsync();
}
void OnCustomizeEditModel(GridCustomizeEditModelEventArgs e) {
if(e.IsNew) {
var editModel = (Employee)e.EditModel;
editModel.EmployeeId = GridDataSource.Count() + 1;
}
}
async Task OnEditModelSaving(GridEditModelSavingEventArgs e) {
var editModel = (Employee)e.EditModel;
if (e.IsNew)
await Northwind.AddAsync(editModel);
else
e.CopyChangesToDataItem();
await Northwind.SaveChangesAsync();
GridDataSource = await Northwind.Employees.ToListAsync();
}
void OnCustomizeDataRowEditor(GridCustomizeDataRowEditorEventArgs e) {
if(e.EditSettings is ITextEditSettings settings)
settings.ShowValidationIcon = true;
}
public void Dispose() {
Northwind?.Dispose();
}
}
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
namespace CustomValidation.Pages {
public class ValidationMessageStoreEventArgs : EventArgs {
public ValidationMessageStoreEventArgs(object model, Dictionary<string, List<string>> errors) {
EditModel = model;
Errors = errors;
}
public object EditModel { get; }
private Dictionary<string, List<string>> Errors { get; }
public void AddError(string fieldName, string errorText) {
if (string.IsNullOrEmpty(fieldName) || string.IsNullOrEmpty(errorText))
return;
if (!Errors.ContainsKey(fieldName))
Errors[fieldName] = new List<string>();
Errors[fieldName].Add(errorText);
}
}
public class MyCustomValidator : ComponentBase {
private ValidationMessageStore MessageStore;
[CascadingParameter]
private EditContext CurrentEditContext { get; set; }
[Parameter]
public Action<ValidationMessageStoreEventArgs> DataItemValidating { get; set; }
protected override void OnInitialized() {
if (CurrentEditContext == null) {
throw new InvalidOperationException(
$"{nameof(MyCustomValidator)} requires a cascading " +
$"parameter of type {nameof(EditContext)}. " +
$"For example, you can use {nameof(MyCustomValidator)} " +
$"inside an {nameof(EditForm)}.");
}
MessageStore = new(CurrentEditContext);
CurrentEditContext.OnValidationRequested += (s, e) => ValidateData();
CurrentEditContext.OnFieldChanged += (s, e) => ValidateData();
}
void ValidateData() {
if (DataItemValidating == null)
return;
var errors = new Dictionary<string, List<string>>();
var args = new ValidationMessageStoreEventArgs(CurrentEditContext.Model, errors);
DataItemValidating.Invoke(args);
MessageStore.Clear();
foreach (var error in errors)
MessageStore.Add(CurrentEditContext.Field(error.Key), error.Value);
CurrentEditContext.NotifyValidationStateChanged();
}
}
}
using System;
using System.Collections.Generic;
namespace CustomValidation.Models;
public partial class Employee {
public int EmployeeId { get; set; }
public string LastName { get; set; } = null!;
public string FirstName { get; set; } = null!;
public string? Title { get; set; }
public DateTime? HireDate { get; set; }
}
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
namespace CustomValidation.Models;
public partial class NorthwindContext : DbContext {
public NorthwindContext() {}
public NorthwindContext(DbContextOptions<NorthwindContext> options) : base(options) {}
public virtual DbSet<Employee> Employees { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
}
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.HasAnnotation("Relational:Collation", "SQL_Latin1_General_CP1_CI_AS");
modelBuilder.Entity<Employee>(entity => {
entity.HasIndex(e => e.EmployeeId, "EmployeeId");
entity.HasIndex(e => e.LastName, "LastName");
entity.HasIndex(e => e.FirstName, "FirstName");
entity.HasIndex(e => e.Title, "Title");
entity.HasIndex(e => e.HireDate, "HireDate");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
using Microsoft.EntityFrameworkCore;
// ...
builder.Services.AddDbContextFactory<NorthwindContext>((sp, options) => {
var env = sp.GetRequiredService<IWebHostEnvironment>();
var dbPath = Path.Combine(env.ContentRootPath, "Northwind.db");
options.UseSqlite("Data Source=" + dbPath);
});
In the following example, the bound database throws an error if a new row’s Field Name is set to Test. A custom validator catches this error and displays the error message in the Grid:
@page "/"
@using CustomValidation.Models
@using Microsoft.EntityFrameworkCore
@using System.ComponentModel.DataAnnotations
@inject IDbContextFactory<NorthwindContext> NorthwindContextFactory
@implements IDisposable
<DxGrid Data="GridDataSource"
EditModelSaving="OnEditModelSaving"
EditMode="GridEditMode.EditRow"
CustomizeDataRowEditor="OnCustomizeDataRowEditor"
CustomizeEditModel="OnCustomizeEditModel"
KeyFieldName="EmployeeId">
<Columns>
<DxGridCommandColumn DeleteButtonVisible="false" />
<DxGridDataColumn FieldName="FirstName" />
<DxGridDataColumn FieldName="LastName" />
<DxGridDataColumn FieldName="Title" />
<DxGridDataColumn FieldName="HireDate" />
</Columns>
<CustomValidators>
<MyCustomValidator @ref="MyValidator" />
</CustomValidators>
</DxGrid>
@code {
MyCustomValidator MyValidator { get; set; }
IEnumerable<object> GridDataSource { get; set; }
NorthwindContext Northwind { get; set; }
protected override async Task OnInitializedAsync() {
Northwind = NorthwindContextFactory.CreateDbContext();
Northwind.EnsureEmployeeFirstNameTriggerExists();
GridDataSource = await Northwind.Employees.ToListAsync();
}
void OnCustomizeEditModel(GridCustomizeEditModelEventArgs e) {
if(e.IsNew) {
var editModel = (Employee)e.EditModel;
editModel.EmployeeId = GridDataSource.Count() + 1;
}
}
async Task OnEditModelSaving(GridEditModelSavingEventArgs e) {
var editModel = (Employee)e.EditModel;
if (e.IsNew)
await Northwind.AddAsync(editModel);
else
e.CopyChangesToDataItem();
try {
await Northwind.SaveChangesAsync();
GridDataSource = await Northwind.Employees.ToListAsync();
}
catch (DbUpdateException ex) {
MyValidator?.ClearErrors();
var errors = new Dictionary<string, List<string>>();
errors.Add(nameof(editModel.FirstName), [ex.GetBaseException().Message]);
MyValidator?.DisplayErrors(errors);
e.Cancel = true;
}
}
void OnCustomizeDataRowEditor(GridCustomizeDataRowEditorEventArgs e) {
if(e.EditSettings is ITextEditSettings settings)
settings.ShowValidationIcon = true;
}
public void Dispose() {
Northwind?.Dispose();
}
}
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
namespace CustomValidation.Pages {
public class MyCustomValidator : ComponentBase {
private ValidationMessageStore? messageStore;
[CascadingParameter]
private EditContext? CurrentEditContext { get; set; }
protected override void OnInitialized() {
if (CurrentEditContext is null) {
throw new InvalidOperationException(
$"{nameof(CustomValidation)} requires a cascading " +
$"parameter of type {nameof(EditContext)}. " +
$"For example, you can use {nameof(CustomValidation)} " +
$"inside an {nameof(EditForm)}.");
}
messageStore = new(CurrentEditContext);
CurrentEditContext.OnValidationRequested += (s, e) => messageStore?.Clear();
CurrentEditContext.OnFieldChanged += (s, e) => messageStore?.Clear(e.FieldIdentifier);
}
public void DisplayErrors(Dictionary<string, List<string>> errors) {
if (CurrentEditContext is not null) {
foreach (var err in errors) {
messageStore?.Add(CurrentEditContext.Field(err.Key), err.Value);
}
CurrentEditContext.NotifyValidationStateChanged();
}
}
public void ClearErrors() {
messageStore?.Clear();
CurrentEditContext?.NotifyValidationStateChanged();
}
}
}
using System;
using System.Collections.Generic;
namespace CustomValidation.Models;
public partial class Employee {
public int EmployeeId { get; set; }
public string LastName { get; set; } = null!;
public string FirstName { get; set; } = null!;
public string? Title { get; set; }
public DateTime? HireDate { get; set; }
}
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
namespace CustomValidation.Models;
public partial class NorthwindContext : DbContext {
public NorthwindContext() {}
public NorthwindContext(DbContextOptions<NorthwindContext> options) : base(options) {}
public virtual DbSet<Employee> Employees { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
}
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.HasAnnotation("Relational:Collation", "SQL_Latin1_General_CP1_CI_AS");
modelBuilder.Entity<Employee>(entity => {
entity.HasIndex(e => e.EmployeeId, "EmployeeId");
entity.HasIndex(e => e.LastName, "LastName");
entity.HasIndex(e => e.FirstName, "FirstName");
entity.HasIndex(e => e.Title, "Title");
entity.HasIndex(e => e.HireDate, "HireDate");
});
OnModelCreatingPartial(modelBuilder);
}
public void EnsureEmployeeFirstNameTriggerExists() {
var connection = Database.GetDbConnection();
bool isClosed = connection.State == System.Data.ConnectionState.Closed;
if (isClosed)
connection.Open();
try {
using var checkCommand = connection.CreateCommand();
checkCommand.CommandText = @"SELECT name FROM sqlite_master WHERE type = 'trigger' AND name = 'RaiseErrorOnFirstName'";
var exists = checkCommand.ExecuteScalar() != null;
if (!exists) {
using var createCommand = connection.CreateCommand();
createCommand.CommandText = @"CREATE TRIGGER RaiseErrorOnFirstName BEFORE INSERT ON Employees FOR EACH ROW WHEN NEW.FirstName = 'Test' BEGIN SELECT RAISE(FAIL, 'FirstName cannot be ''Test'''); END;";
createCommand.ExecuteNonQuery();
}
}
finally {
if (isClosed)
connection.Close();
}
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
using Microsoft.EntityFrameworkCore;
// ...
builder.Services.AddDbContextFactory<NorthwindContext>((sp, options) => {
var env = sp.GetRequiredService<IWebHostEnvironment>();
var dbPath = Path.Combine(env.ContentRootPath, "Northwind.db");
options.UseSqlite("Data Source=" + dbPath);
});
See Also