Blazor University (35)表单 —— 编写自定义验证

原文链接:https://blazor-university.com/forms/writing-custom-validation/

编写自定义验证

源代码[1]

请注意,与有关 EditContext、FieldIdentifiers 和 FieldState[2] 的部分一样,这是一个高级主题。

如前所述,FieldState 类保存表单数据的元状态。除了指示值是否已被手动编辑外,Blazor 还存储验证错误消息的集合。为了了解它的工作原理,本节将说明如何创建我们自己的自定义验证机制,该机制可与 Blazor 一起使用来验证用户输入。

下面的 UML 图显示了 EditForm 和存储此元状态的各种类(在图中分组)之间的关系。请记住,每当 EditForm.Model 发生更改时,EditForm 都会创建一个新的 EditContext 实例。然后可以对以前的 EditContext(不再需要它,因为它包含有关以前模型的信息)进行垃圾收集,并且可以对图表中分组的所有类实例进行垃圾收集。

f84a655063986b45cb2cc8984537f5b1.png

我们的自定义验证将基于 FluentValidation[3]。完成本节后(或者如果您只是想要一些可以立即使用的东西),请查看 blazor-validation[4]

创建验证器组件

我们的验证器组件不必为了提供验证而从任何特定类继承。唯一的要求是它来自 Blazor ComponentBase 类,以便我们可以将其添加到视图中的 <EditForm> 标记中。嵌入 <EditForm> 标记的目的是为了让我们可以定义一个级联参数[5],以便在 EditFormModel 参数更改时获取当前由 EditForm 创建的 EditContext

首先,创建一个新的 Blazor 应用并添加对 FluentValidation NuGet 包[6]的引用。然后创建一个名为 FluentValidationValidator 的类。

public class FluentValidationValidator : ComponentBase
{[CascadingParameter]private EditContext EditContext { get; set; }[Parameter]public Type ValidatorType { get; set; }private IValidator Validator;private ValidationMessageStore ValidationMessageStore;[Inject]private IServiceProvider ServiceProvider { get; set; }
}
  • EditContext

    从其父 <EditForm> 组件传递给我们的组件的级联参数。每次 EditForm.Model 更改时,这都会更改。

  • ValidatorType

    这将指定用于执行实际验证的类类型。我们将检查这是一个 IValidator(一个 FluentValidation 接口)。

  • Validator

    这将保存对指定 ValidatorType 实例的引用,以执行实际的对象验证。

  • ValidationMessageStore

    每次我们的 EditContext 更改(因为 EditForm.Model 已更改)时,我们都会创建一个新的。

  • ServiceProvider

    IServiceProvider 的注入依赖项,我们可以使用它来创建 ValidatorType 的实例。

public override async Task SetParametersAsync(ParameterView parameters)
{// Keep a reference to the original values so we can check if they have changedEditContext previousEditContext = EditContext;Type previousValidatorType = ValidatorType;await base.SetParametersAsync(parameters);if (EditContext == null)throw new NullReferenceException($"{nameof(FluentValidationValidator)} must be placed within an {nameof(EditForm)}");if (ValidatorType == null)throw new NullReferenceException($"{nameof(ValidatorType)} must be specified.");if (!typeof(IValidator).IsAssignableFrom(ValidatorType))throw new ArgumentException($"{ValidatorType.Name} must implement {typeof(IValidator).FullName}");if (ValidatorType != previousValidatorType)ValidatorTypeChanged();// If the EditForm.Model changes then we get a new EditContext// and need to hook it upif (EditContext != previousEditContext)EditContextChanged();
}
  • 第 4-5 行

    每当我们的参数之一更改(包括我们的 EditContext 级联参数)时,都会执行 SetParametersAsync。我们需要做的第一件事是保留对一些原始值的引用,以便我们可以查看它们是否已更改并做出相应的反应。

  • 第 7 行

    调用 base.SetParametersAsync 会将我们对象的属性更新为新值。

  • 第 9-16 行

    确保我们有一个 EditContext 和一个作为 IValidatorValidatorType

  • 第 18-19 行

    如果 ValidatorType 已更改,那么我们需要创建该类型的新实例并将其分配给我们的私有 Validator 字段以验证我们的 EditContext.Model

  • 第 23-24 行

    如果 EditContext 发生了变化,那么我们需要连接一些事件以便我们可以验证用户输入,并且我们需要一个新的 ValidationMessageStore 来存储任何验证错误。

创建一个新的 ValidatorType 实例就像指示我们的 ServiceProvider 检索一个实例一样简单。

private void ValidatorTypeChanged()
{Validator = (IValidator)ServiceProvider.GetService(ValidatorType);
}

为此,我们必须在应用程序的 Startup.ConfigureServices 方法中注册验证器——一旦我们有了验证器和要验证的东西,我们就会这样做。

每当 EditContext 发生变化时,我们都需要一个新的 ValidationMessagesStore 来存储我们的验证错误消息。

void EditContextChanged()
{ValidationMessageStore = new ValidationMessageStore(EditContext);HookUpEditContextEvents();
}

我们还需要连接一些事件,以便我们可以验证用户输入并将错误添加到我们的 ValidationMessageStore

private void HookUpEditContextEvents()
{EditContext.OnValidationRequested += ValidationRequested;EditContext.OnFieldChanged += FieldChanged;
}
  • OnValidationRequested

    当需要验证 EditContext.Model 的所有属性时会触发此事件。当用户尝试发布 EditForm 以便 Blazor 可以确定输入是否有效时,会发生这种情况。

  • OnFieldChanged

    每当用户通过在 Blazor 的 InputBase<T> 派生组件之一中编辑 EditContext.Model 的属性值来更改它时,都会触发此事件。

async void ValidationRequested(object sender, ValidationRequestedEventArgs args)
{ValidationMessageStore.Clear();var validationContext =new ValidationContext<object>(EditContext.Model);ValidationResult result =await Validator.ValidateAsync(validationContext);AddValidationResult(EditContext.Model, result);
}
  • 第 3 行

    首先,我们从任何先前的验证中清除所有错误消息。

  • 第 4 行

    接下来,我们指示 FluentValidation.IValidator 验证在 EditForm(我们通过 EditContext.Model 访问)中正在编辑的模型。

  • 第 5 行

    最后,我们将任何验证错误添加到我们的 ValidationMessageStore,这是在一个单独的方法中完成的,因为我们将在验证整个对象时使用它,并且在通过 EditContext.OnFieldChanged 通知时验证单个更改的属性时也会使用它。

将错误消息添加到 ValidationMessageStore 只是创建一个 FieldIdentifier 以准确识别哪个对象/属性有错误并使用该标识符添加任何错误消息,然后让 EditContext 知道验证状态已更改的情况。

请注意,当验证涉及长时间运行的异步调用(例如,对 WebApi 以检查 UserName 可用性)时,我们可以更新验证错误并多次调用 EditContext.NotifyValidationStateChanged 以在用户界面中提供验证状态的增量显示。

void AddValidationResult(object model, ValidationResult validationResult)
{foreach (ValidationFailure error in validationResult.Errors){var fieldIdentifier = new FieldIdentifier(model, error.PropertyName);ValidationMessageStore.Add(fieldIdentifier, error.ErrorMessage);}EditContext.NotifyValidationStateChanged();
}

最后,当用户在表单输入控件中编辑值时,我们需要验证单个对象/属性。当发生这种情况时,我们会通过 EditContext.OnFieldChanged 事件得到通知。除了前两行和最后一行,以下代码是 FluentValidator 特定的。

async void FieldChanged(object sender, FieldChangedEventArgs args)
{FieldIdentifier fieldIdentifier = args.FieldIdentifier;ValidationMessageStore.Clear(fieldIdentifier);var propertiesToValidate = new string[] { fieldIdentifier.FieldName };var fluentValidationContext =new ValidationContext<object>(instanceToValidate: fieldIdentifier.Model,propertyChain: new FluentValidation.Internal.PropertyChain(),validatorSelector: new FluentValidation.Internal.MemberNameValidatorSelector(propertiesToValidate));ValidationResult result = await Validator.ValidateAsync(fluentValidationContext);AddValidationResult(fieldIdentifier.Model, result);
}
  • 第 3-4 行

    从事件 args 中获取 FieldIdentifier(ObjectInstance/PropertyName 对),并仅清除该属性的所有先前错误消息。

  • 第 16 行

    使用 ValidationRequested 使用的相同方法将来自 FluentValidation 的错误添加到我们的 ValidationMessageStore

使用组件

首先创建一个模型供我们的用户编辑。

namespace CustomValidation.Models
{public class Person{public string Name { get; set; }public int Age { get; set; }}
}

接下来,使用 FluentValidation 为 Person 创建一个验证器。

using CustomValidation.Models;
using FluentValidation;namespace CustomValidation.Validators
{public class PersonValidator : AbstractValidator<Person>{public PersonValidator(){RuleFor(x => x.Name).NotEmpty();RuleFor(x => x.Age).InclusiveBetween(18, 80);}}
}

因为我们的验证组件使用 IServiceProvider 来创建验证器的实例,所以我们需要在 Startup.ConfigureServices 中注册它。

public void ConfigureServices(IServiceCollection services)
{services.AddScoped<Validators.PersonValidator>();
}

最后,我们需要设置我们的用户界面来编辑我们的 Person 类的一个实例。

@page "/"
@using Models<EditForm Model=@Person OnValidSubmit=@ValidFormSubmitted><FluentValidationValidator ValidatorType=typeof(Validators.PersonValidator)/><p>Validation summary</p><ValidationSummary /><p>Edit object</p><div class="form-group"><label for="Name">Name</label><InputText @bind-Value=Person.Name class="form-control" id="Name" /><ValidationMessage For="() => Person.Name" /></div><div class="form-group"><label for="Age">Age</label><InputNumber @bind-Value=Person.Age class="form-control" id="Age" /><ValidationMessage For=@(() => Person.Age) /></div><input type="submit" class="btn btn-primary" value="Save" />
</EditForm>@code {Person Person = new Person();void ValidFormSubmitted(){Person = new Person();}
}

执行流程

页面显示

  1. 我们的 EditForm 组件是从 <EditForm Model=@Person> 标记创建的。

  2. EditForm.OnParametersSet 被执行,因为 EditForm.Model 已经从 null 变成了我们的 Person,它创建了一个新的 EditContext 实例。

  3. 新的 EditContext 实例通过级联值[7]级联到所有子组件。

  4. 对于这种级联值的变化,InputBase<T> 的每个派生类都会执行其 SetParametersAsync,并通过创建 FieldIdentifier 的新实例来做出反应。

我们的验证组件已初始化

  1. 我们的验证组件的 SetParametersAsync 方法是通过引用新的 EditContext 来执行的。

  2. 我们的组件创建了一个新的 ValidationMessageStore

  3. 我们的组件侦听 EditContext 上的事件以获取验证请求和输入更改通知。

用户更改数据

  1. 用户在 InputBase<T> 派生类中编辑数据。

  2. 组件通过 EditContext.NotifyFieldChanged 通知此状态更改(从未修改到已修改),并传递其 FieldIdentifier

  3. EditContext 触发其 OnFieldChanged,传递 FieldIdentifier

  4. 我们组件的事件订阅告诉我们的 ValidationMessageStore 清除由 FieldIdentifierModelFieldName 属性标识的状态的所有错误消息。

  5. 我们的组件对单个属性执行其自定义验证。

  6. 验证错误被添加到我们组件的 ValidationMessageStore 中,由 FieldIdentifier 作为键。

  7. ValidationMessageStore 执行 EditContext.GetFieldState 以检索当前 FieldIdentifierFieldState

  8. ValidationMessageStore 被添加到 FieldState,以便 FieldState.GetValidationMessages 能够从所有 ValidationMessageStore 实例中检索所有错误消息。

第 8 步特别重要,因为 Blazor 需要能够检索特定输入的所有验证错误消息,无论它们被添加到哪个 ValidationMessageStore

用户提交表单

  1. <EditForm> 执行 EditContext.Validate

  2. EditContext 触发其 OnValidationRequested 事件。

  3. 我们组件的事件订阅告诉我们的 ·ValidationMessageStore· 清除所有字段的所有先前验证错误消息。

  4. 我们的组件对整个 EditContext.Model 对象执行其自定义验证。

  5. 与对单个更改的验证一样,错误被添加到 ·ValidationMessageStore·,它使用 ·EditContext· 中的所有相关 ·FieldState· 实例注册自身。

  6. <EditForm> 根据是否有错误消息触发相关的有效/无效事件。

EditForm.Model 已更改

如果这是一个用于创建 people 的用户界面,那么在成功将我们的新 People 提交到服务器后,我们的应用程序可能会创建一个新 People 供我们的表单进行编辑。这将丢弃与前一个  People 实例相关的所有状态(在虚线框中表示),并从新实例重新开始。

  • EditContext

  • FieldState

  • ValidationMessageStore

b31f838550bdf3ea6b20ca82dd2331ee.png

在演示代码中添加了一些日志记录,我们会看到以下输出。

WASM:编辑上下文已更改
WASM:创建了新的 ValidationMessageStore
WASM:连接 EditContext 事件(OnValidationRequested 和 OnFieldChanged)
WASM:触发 OnFieldChanged:验证类 Person 上名为 Name 的单个属性
WASM:触发 OnFieldChanged:验证 Person 类上名为 Age 的单个属性
WASM:OnValidationRequested 触发:验证整个对象
WASM:EditContext 已更改
WASM:创建了新的 ValidationMessageStore
WASM:连接 EditContext 事件(OnValidationRequested 和 OnFieldChanged)
  • 第 1-3 行

    创建相关状态实例以支持编辑 Person 实例。

  • 第 4 行

    输入了一个名字

  • 第 5 行

    输入年龄

  • 第 6 行

    用户提交表单

  • 第 7 行

    Index.razor 中的 Person 实例被更改,导致元状态实例被丢弃并为新的 EditForm.Model 创建新实例。

参考资料

[1]

源代码: https://github.com/mrpmorris/blazor-university/tree/master/src/Forms/CustomValidation

[3]

FluentValidation: https://github.com/JeremySkinner/FluentValidation

[4]

blazor-validation: https://github.com/mrpmorris/blazor-validation

[6]

FluentValidation NuGet 包: https://www.nuget.org/packages/FluentValidation/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/284886.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HTML 元素内部添加预加载

CSS&#xff1a; /*元素内部加载loading*/.innerLoading {height: 100%;width: 100%;display: flex;justify-content: center;align-items: center;}.innerLoading * {text-align: center;color: #737782cc;fill: #73777A;font-size: 1em !important;font-family: SimSun,SimHe…

Windows下怎样安装Tomcat

Tomcat 是开源的WEB应用容器&#xff0c;所以受到各位程序员和公司的亲赖。在这里给大家介绍一下如何在Windows环境下安装Tomcat绿色版本&#xff0c;希望能够对大家有帮助。 1.首先去Tomcat官网下载Tomcat软件&#xff0c;在百度中搜索Tomcat,进入英文网址http://tomcat.apach…

智能识别云服务端平台之神【合合信息TextIn】

一、前言 众所周知&#xff0c;随着互联网和人工智能的发展&#xff0c;我们非常多的场景需要用到智能“识别”功能&#xff0c;比如人脸识别、通用文字识别、表格识别、办公文档识别、身份证、名片、营业执照等国内外卡证文字识别等等&#xff0c;同时识别与理解面临的全球性技…

【ArcGIS微课1000例】0015:ArcGIS如何创建/自定义快捷键?

为了提高工作效率,强大的ArcGIS提供了很多快捷键,如访问 ArcMap 菜单命令、窗口操纵、刷新或暂停地图绘制、通过拖放进行移动或复制等等。本文在ArcGIS已有快捷键的基础之上,为了提高工作效率,讲解如何定制个性化的快捷键。 参考阅读:【ArcGIS风暴】ArcGIS快捷键大全 文章…

Bresenham 算法

1965 年&#xff0c;Bresenham 为数字绘图仪开发了一种绘制直线的算法&#xff0c;该算法同样使用于光栅扫描显示器&#xff0c;被称为 Bresenham 算法。 原理 算法的目标是选择表示直线的最佳光栅位置。Bresenhan 算法在主位移方向上每次递增一个单位。另一个方向的增量为 0…

ML.NET 更新

点击上方蓝字关注我们&#xff08;本文阅读时间&#xff1a;5分钟)ML.NET是一款面向.NET开发人员的开源&#xff0c;跨平台机器学习框架&#xff0c;可以将自定义机器学习集成到.NET应用中。我们很开心地向您介绍我们在过去几个月中所做的工作。ML.NET:https://dotnet.microsof…

Andriod之提示java.lang.SecurityException: getDataNetworkTypeForSubscriber导致程序奔溃

1、问题 修改targetSdkVersion 33 适配Android13后4G网络环境被其它app拉起来提示这个异常 2、原因 我们定位到代码在这行函数 telephonyManager.getNetworkType()Android11 的权限有关,由于缺少该权限导致无法访问接口而提示安全异常 3、解决办法 方法1:我们直接申请RE…

[译]基于GPU的体渲染高级技术之raycasting算法

[译]基于GPU的体渲染高级技术之raycasting算法 PS&#xff1a;我决定翻译一下《Advanced Illumination Techniques for GPU-Based Volume Raycasting》。像我翻译其他资料一样&#xff0c;只按我的需要和观点来翻译。有的部分详细翻译&#xff0c;附加注解&#xff0c;有的部分…

【GIS风暴】什么是地理空间智能(Geospatial AI)?

人工智能(Artificial Intelligence,AI)已经成为新技术革命下一阶段的热词,也成为未来产业的驱动力量。使用智能算法,数据分类和智能预测、分析,AI在很多领域将有一系列的工具来帮助解决问题。 将AI用于GIS这一具体的领域的分析、方法和解决方案,就叫地理空间智能(Geos…

JavaScript 清除图片背景颜色 使之透明

主要JS /**清除图片背景颜色 **/ function removeImgBg(img) {//背景颜色 白色const rgba [255, 255, 255, 255];// 容差大小const tolerance 60;var imgData null;const [r0, g0, b0, a0] rgba;var r, g, b, a;const canvas document.createElement(canvas);const cont…

day01基础部分

一、python是什么样的语言 1、编译型语言和解释型语言&#xff0c;python是解释型语言 1.1、编译型语言就是把源程序代码一次性翻译成机器码&#xff08;计算机可识别的代码&#xff09;&#xff0c;然后交给计算机去运行&#xff0c;一般需经过编译&#xff08;compile&#x…

WPF 制作 Windows 屏保

分享如何使用WPF 制作 Windows 屏保WPF 制作 Windows 屏保作者&#xff1a;驚鏵原文链接&#xff1a;https://github.com/yanjinhuagood/ScreenSaver框架使用.NET452&#xff1b;Visual Studio 2019;项目使用 MIT 开源许可协议&#xff1b;更多效果可以通过GitHub[1]|码云[2]下…

Windows 7 下右键发送到菜单项没了

为什么80%的码农都做不了架构师&#xff1f;>>> 问题描述: 突然有一天,Windows 7 下右键发送到菜单项没了,如图所示: 问题原因 黑人问号脸? 转载于:https://my.oschina.net/taadis/blog/1591398

【ArcGIS微课1000例】0016:ArcGIS书签操作(添加书签、管理书签)知多少?

书签可以将地图数据的某一视图状态保存起来,以便在使用时打开书签,直接回到这一视图状态。可创建多个书签以便快速回到不同的视图状态,也可以对书签进行管理。 文章目录 1 创建书签2 管理书签注意:书签只针对空间数据,在【布局视图】中是不能创建书签的。 1 创建书签 可…

分享一个WPF 实现 Windows 软件快捷小工具

分享一个WPF 实现 Windows 软件快捷小工具Windows 软件快捷小工具作者&#xff1a;WPFDevelopersOrg原文链接&#xff1a;https://github.com/WPFDevelopersOrg/SoftwareHelper框架使用.NET40&#xff1b;Visual Studio 2019;项目使用 MIT 开源许可协议&#xff1b;项目使用 MV…

学习环境配置:Manjaro、MSYS2以及常见软件

0.前言 在说Manjaro之前&#xff0c;要先说一下Linux发行版。对于各大发行版而言&#xff0c;内核只有版本的差异&#xff0c;最重要的区别就是包管理系统。常见的包管理系统包括&#xff1a;Pacman&#xff0c;Apt , Yum和Portage。在学习Linux的过程中&#xff0c;和大数人一…

【ArcGIS微课1000例】0017:ArcGIS测量距离和面积工具的巧妙使用

文章目录 1 交互式测量2 测量要素ArcGIS提供了快速测量距离和面积的工具,通过测量工具可对地图中的线和面进行测量。 工具条: 测量工具位于【工具】工具条上,如下图所示: 测量界面: 功能按钮简介: 可使用此工具在地图上绘制一条线或者一个面,然后获取线的长度与面的面…

[转]HTTP/3 未来可期?

2015 年 HTTP/2 标准发表后&#xff0c;大多数主流浏览器也于当年年底支持该标准。此后&#xff0c;凭借着多路复用、头部压缩、服务器推送等优势&#xff0c;HTTP/2 得到了越来越多开发者的青睐&#xff0c;不知不觉的 HTTP 已经发展到了第三代。本文基于兴趣部落接入 HTTP/3 …

【ArcGIS微课1000例】0018:ArcGIS设置相对路径和数据源

文章目录 ArcGIS设置相对路径ArcGIS设置数据源ArcGIS设置相对路径 菜鸟们在使用ArcGIS时经常会碰到将地图文档(.mxd)拷贝到别的电脑上或改变一个路径时,出现数据丢失的现象,具体表现为图层前面出现一个红色的感叹号,如下图所示。 出现以上问题的根本原因是数据GSS.tif的原…

AI 之 OpenCvSharp 安卓手机摄像头识别人脸

OpenCvSharp是OpenCv的包装器&#xff0c;相当于底层是OpenCv只是用.Net的方式调用底层的接口的实现&#xff0c;所以&#xff0c;从OpenCv的知识架构来讲&#xff0c;源码是一样一样的。就是换个语言写而已。1. OpenCvSharp 尽可能地以原生 OpenCV C/C API 风格为蓝本。2. Ope…