目录
概述——Blazor ValidationFormState控件
概述——Blazor ValidationFormState控件
这是描述一组有用的Blazor Edit控件的系列文章中的第二篇,这些控件解决了开箱即用编辑体验中的一些当前缺点,而无需购买昂贵的工具包。
本文介绍了表单验证的工作原理,并展示了如何从头开始构建一个相对简单但功能齐全的验证系统。一旦定义了基本结构和类,就很容易为自定义类的任何新验证要求或验证器编写额外的验证链方法。

代码和示例
该存储库包含一个项目,该项目实现了本系列中所有文章的控件。你可以在这里找到它。
示例站点位于https://cec-blazor-database.azurewebsites.net/。
本文末尾描述的示例表单可以在https://cec-blazor-database.azurewebsites.net//validationeditor 中看到。
Repo是未来文章的一项正在进行的工作,因此会发生变化和发展。
Blazor编辑设置
首先,让我们看看开箱即用的表单控件以及验证的工作原理。一个经典的形式看起来像这样:
第一篇文章介绍的基本相互作用EditForm和EditContext因此我们将跳过该部分并将精力集中在验证过程。
当用户单击提交按钮时,EditForm可以:
- 如果使用OnSubmit注册了委托,则会触发它并忽略验证。
- 如果没有OnSubmit委托,它会调用EditContext.Validate.。根据结果触发OnValidSubmit或OnInvalidSubmit。
EditContext.Validate检查是否有为OnValidationRequested注册的委托,如果有,则同步运行它。完成后,它会检查ValidationMessageStore中是否有任何消息。如果为空,则表单通过验证并被OnValidSubmit调用,否则OnInvalidSubmit被调用。
Validator是一个没有发出标记的表单组件。它被放置在EditForm中并捕获级联的EditContext.。在初始化时,它注册一个事件处理程序EditContext.OnValidationRequested以触发验证。在验证时,验证器执行它编码要做的任何事情,将验证失败消息记录到EditContext ValidationMessageStore并最终调用EditContext.NotifyValidationStateChanged,其将触发 EditContext.OnValidationStateChanged。
验证控件
诸如ValidationMessage和ValidationSummary捕获级联EditContex和在EditContext.OnValidationStateChanged上注册事件处理程序之类的控件。触发时,它们会检查任何相关消息并显示它们。
在上面显示的窗体中,
验证器
Validator是基本的验证器类。它被声明额为abstract并使用泛型。验证器按照链接原则工作。基类包含所有常见的样板代码。
- 第一次调用是针对要验证的对象类型定义的扩展方法。每个对象类型都需要自己的扩展方法来调用其特定的验证器。此扩展方法返回对象类型的适当验证器。
- 拥有验证器实例后,您可以将任意数量的验证方法链接在一起。每个都经过编码以运行其验证测试,将任何特定消息记录到验证器,必要时触发行程,并返回验证器实例。
- 验证通过调用Validate完成,如有必要,它会触发传递的绊线,并将所有验证消息记录到ValidationMessageStore中。
该Validator属性/字段有:
public bool IsValid => !Trip; public List
Messages { get; } = new List
(); protected bool Trip { get; set; } = false; protected string FieldName { get; set; } protected T Value { get; set; } protected string DefaultMessage { get; set; } = "The value failed validation"; protected ValidationMessageStore ValidationMessageStore { get; set; } protected object Model { get; set; }
构造函数填充validator:
public Validator(T value, string fieldName, object model, ValidationMessageStore validationMessageStore, string message) { this.FieldName = fieldName; this.Value = value; this.Model = model; this.ValidationMessageStore = validationMessageStore; this.DefaultMessage = string.IsNullOrWhiteSpace(message) ? this.DefaultMessage : message; }
有两种Validate方法:一种供外部使用的public方法,另一种供特定验证器覆盖的protected方法。
public virtual bool Validate(ref bool tripwire, string fieldname, string message = null) { if (string.IsNullOrEmpty(fieldname) || this.FieldName.Equals(fieldname)) { this.Validate(message); if (!this.IsValid) tripwire = true; } else this.Trip = false; return this.IsValid; }
protected virtual bool Validate(string message = null) { if (!this.IsValid) { message ??= this.DefaultMessage; // Check if we've logged specific messages. If not, add the default message if (this.Messages.Count == 0) Messages.Add(message); //set up a FieldIdentifier and //add the message to the Edit Context ValidationMessageStore var fi = new FieldIdentifier(this.Model, this.FieldName); this.ValidationMessageStore.Add(fi, this.Messages); } return this.IsValid; } protected void LogMessage(string message) { if (!string.IsNullOrWhiteSpace(message)) Messages.Add(message); }
StringValidator
让我们看一下StringValidator验证器的示例实现。全套验证器在Repo中。有两个类:
- StringValidatorExtensions是一个声明为string扩展方法的static类。
- StringValidator是Validator专门为string的实现。
StringValidatorExtensions为string声明了一个静态扩展方法Validation。它返回一个StringValidator实例。在任何string上调用StringValidator来初始化验证链。
public static class StringValidatorExtensions { public static StringValidator Validation(this string value, string fieldName, object model, ValidationMessageStore validationMessageStore, string message = null) { var validation = new StringValidator(value, fieldName, model, validationMessageStore, message); return validation; } }
StringValidator继承自Validator并声明了strings的特定验证链方法。每个都运行它的测试。如果验证失败,它会将任何提供的消息记录到消息存储中并触发绊线。最后,它返回this。对于string,我们有两种长度方法和一种覆盖大多数情况的RegEx方法。
public class StringValidator : Validator
{ public StringValidator(string value, string fieldName, object model, ValidationMessageStore validationMessageStore, string message) : base(value, fieldName, model, validationMessageStore, message) { } public StringValidator LongerThan(int test, string message = null) { if (string.IsNullOrEmpty(this.Value) || !(this.Value.Length > test)) { Trip = true; LogMessage(message); } return this; } public StringValidator ShorterThan(int test, string message = null) { if (string.IsNullOrEmpty(this.Value) || !(this.Value.Length < test)) { Trip = true; LogMessage(message); } return this; } public StringValidator Matches(string pattern, string message = null) { if (!string.IsNullOrWhiteSpace(this.Value)) { var match = Regex.Match(this.Value, pattern); if (match.Success && match.Value.Equals(this.Value)) return this; } this.Trip = true; LogMessage(message); return this; } }
IValidation
该IValidation接口看起来是这样的。它只是定义了一个Validate方法。
public interface IValidation { public bool Validate(ValidationMessageStore validationMessageStore, string fieldname, object model = null); }
WeatherForecast
WeatherForecast 是典型的数据类。
- 它实现IValidation以便控件可以运行验证。
- 每个字段都声明为具有默认值的属性。
- 它实现了调用三个验证的IValidation.Validate。
每次验证:
- 调用Validation类型的扩展方法。
- 调用一个或多个验证链方法。
- 调用Validate以将任何验证消息记录到EditContext上的ValidationMessageStore中,并且在必要时触发绊线。
public class WeatherForecast : IValidation { public int ID { get; set; } = -1; public DateTime Date { get; set; } = DateTime.Now; public int TemperatureC { get; set; } = 0; [NotMapped] public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); public string Summary { get; set; } = string.Empty; public bool Validate(ValidationMessageStore validationMessageStore, string fieldname, object model = null) { model = model ?? this; bool trip = false; this.Summary.Validation("Summary", model, validationMessageStore) .LongerThan(2, "Your description needs to be a little longer! 3 letters minimum") .Validate(ref trip, fieldname); this.Date.Validation("Date", model, validationMessageStore) .NotDefault("You must select a date") .LessThan(DateTime.Now.AddMonths(1), true, "Date can only be up to 1 month ahead") .Validate(ref trip, fieldname); this.TemperatureC.Validation("TemperatureC", model, validationMessageStore) .LessThan(70, "The temperature must be less than 70C") .GreaterThan(-60, "The temperature must be greater than -60C") .Validate(ref trip, fieldname); return !trip; } }
ValidationFormState控件
该ValidationFormState控件替换了Blazor提供的基本Validator控件。
- 它捕获级联的EditContext.
- DoValidationOnFieldChange控制字段级验证。如果是true,它会在用户退出字段时验证该字段。如果是false,它仅通过EditContext响应表单级验证请求。
- ValidStateChanged 是父级在需要时附加事件处理程序的回调。
- IsValid是一个公开当前验证状态的public readonly属性。它检查EditContext是否有任何验证消息。
- ValidationMessageStore是EditContext的ValidationMessageStore。
- validating 是一个布尔字段,以确保我们不会堆叠验证。
- disposedValue是IDisposable实现的一部分。
[CascadingParameter] public EditContext EditContext { get; set; } [Parameter] public bool DoValidationOnFieldChange { get; set; } = true; [Parameter] public EventCallback
ValidStateChanged { get; set; } public bool IsValid => !EditContext?.GetValidationMessages().Any() ?? true; private ValidationMessageStore validationMessageStore; private bool validating = false; private bool disposedValue;
当组件初始化时,它从EditContext中获取ValidationMessageStore 。它检查它的是否运行字段级验证,如果是的话,使用EditContext.OnFieldChanged事件注册FieldChanged。最后,它使用EditContext.OnValidationRequested注册ValidationRequested。
protected override Task OnInitializedAsync() { Debug.Assert(this.EditContext != null); if (this.EditContext != null) { // Get the Validation Message Store from the EditContext this.validationMessageStore = new ValidationMessageStore(this.EditContext); // Wires up to the EditContext OnFieldChanged event if (this.DoValidationOnFieldChange) this.EditContext.OnFieldChanged += FieldChanged; // Wires up to the Editcontext OnValidationRequested event this.EditContext.OnValidationRequested += ValidationRequested; } return Task.CompletedTask; }
两个事件处理程序调用Validate,一个有字段名,一个没有字段名。
private void FieldChanged(object sender, FieldChangedEventArgs e) => this.Validate(e.FieldIdentifier.FieldName); private void ValidationRequested(object sender, ValidationRequestedEventArgs e) => this.Validate();
Validate里面的评论解释了它在做什么。它将 Model转换为IValidator并检查它是否有效。如果是,则调用接口上的Validate方法。我们见过模型。Validate在WesatherForecast数据类中。当它传递一个fieldname到 Validate时,它只清除该特定fieldname的任何验证消息。
private void Validate(string fieldname = null) { // Checks to see if the Model implements IValidation var validator = this.EditContext.Model as IValidation; if (validator != null || !this.validating) { this.validating = true; // Check if we are doing a field level or form level validation // Form level - clear all validation messages // Field level - clear any field specific validation messages if (string.IsNullOrEmpty(fieldname)) this.validationMessageStore.Clear(); else validationMessageStore.Clear (new FieldIdentifier(this.EditContext.Model, fieldname)); // Run the IValidation interface Validate method validator.Validate(validationMessageStore, fieldname, this.EditContext.Model); // Notify the EditContext that the Validation State has changed // This precipitates a OnValidationStateChanged event // which the validation message controls are all plugged into this.EditContext.NotifyValidationStateChanged(); // Invoke ValidationStateChanged this.ValidStateChanged.InvokeAsync(this.IsValid); this.validating = false; } }
其余代码由实用程序方法和IDisposable实现组成。
public void Clear() => this.validationMessageStore.Clear(); // IDisposable Implementation protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { if (this.EditContext != null) { this.EditContext.OnFieldChanged -= this.FieldChanged; this.EditContext.OnValidationRequested -= this.ValidationRequested; } } disposedValue = true; } } public void Dispose() { // Do not change this code. // Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); GC.SuppressFinalize(this); }
一个简单的实现
为了测试组件,这里有一个简单的测试页面。
上下更改温度,您应该会看到按钮更改颜色和文本,以及启用/禁用状态。将温度更改为 200以获取验证消息。
您可以在https://cec-blazor-database.azurewebsites.net//validationeditor 上看到这一点。
@using Blazor.Database.Data @page "/validationeditor"
Validation Messages:
@code { protected bool _isDirty = false; protected bool _isValid => validationFormState?.IsValid ?? true; protected string btnStateColour => _isDirty ? "btn-danger" : "btn-success"; protected string btnStateText => _isDirty ? "Dirty" : "Clean"; protected string btnValidColour => !_isValid ? "btn-danger" : "btn-success"; protected string btnValidText => !_isValid ? "Invalid" : "Valid"; protected bool _btnSubmitDisabled => !(_isValid && _isDirty); protected EditFormState editFormState { get; set; } protected ValidationFormState validationFormState { get; set; } private WeatherForecast Model = new WeatherForecast() { ID = 1, Date = DateTime.Now, TemperatureC = 22, Summary = "Balmy" }; private void HandleValidSubmit() => this.editFormState.UpdateState(); private void EditStateChanged(bool editstate) => this._isDirty = editstate; }
总结
希望我已经解释了验证的工作原理以及如何构建一个简单但全面且可扩展的验证系统。
验证最常见的问题是ValidationMessage控件不显示消息。造成这种情况的原因通常有两个:
- UI没有更新。单步执行代码以检查何时发生了什么。
- 从ValidationMessage的For属性生成的FieldIdentifier与验证存储中的FieldIdentifier不匹配。检查您正在生成并登录到验证存储的FieldIdentifier。
下一篇文章将展示如何在表单变脏时锁定表单并阻止导航。
如果您在以后发现这篇文章很好,最新版本将在此处提供。
https://www.codeproject.com/Articles//A-Blazor-Validation-Control
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/203867.html原文链接:https://javaforall.net
