第四章 表单(3)- 表单验证
在Blazor中,表单的验证可以通过两种方式实现,一种是使用Blazor所提供表单验证特性,另一种是使用ValidationMessageStore
进行验证。
表单验证的基础使用(内置特性)
一、内置特性表单验证的开启
Blazor中,使用表单组件<EditForm>
时,如果希望开启表单的验证功能,需要在表单中使用<DataAnnotationsValidator/>
和<ValidationSummary/>
或<ValidationMessage>
组件。
1、开启特性验证系统
在表单中使用<DataAnnotationsValidator>
组件可以将数据特性验证附加到关联的EditContext
,从而开启特性验证系统(根据表单模型属性的特性声明或IValidatableObject
接口的Validate
方法进行验证)。
Blazor执行验证有两个节点:
- 当用户从某个字段中失焦时,将执行字段验证。 在字段验证期间,
DataAnnotationsValidator
组件将报告的所有验证结果与该字段相关联。 - 当用户提交表单时,将执行模型验证, 在模型验证期间,
DataAnnotationsValidator
组件尝试根据验证结果报告的成员名称来确定字段,与单个成员无关的验证结果将与模型而不是字段相关联。
2、验证信息展示组件
特定验证信息的展示组件
如果要展示指定字段的验证失败信息,可以使用<ValidationMessage>
组件,并通过For
组件参数设置要进行信息展示的对应属性。
综合验证信息的展示组件
如果想要展示表单中所有的验证失败信息,可以直接使用<ValidationSummary>
组件,如果有需要,也可以通过其Model
组件参数来设置对特定的模型。
需要注意的是,这两个组件都依赖于 ValidationMessageStore
类来获取表单字段的验证错误消息,当ValidationMessageStore
对象中存在验证失败消息时,就可以通过上面的两个组件进行展示。
样式
<ValidationMessage>
和<ValidationSummary>
组件支持任意属性,验证失败的信息将添加到生成的 <div>
或 <ul>
元素中。
验证信息的CSS样式则通过wwwroot/css/app.css
或 wwwroot/css/site.css
进行设置,如:
.validation-message {color: red;
}
-
示例
@page "/AnnotationsTest" @using System.ComponentModel.DataAnnotations<EditForm Model="Model" OnValidSubmit="Submit" FormName="Form1"><DataAnnotationsValidator /><ValidationSummary /><div class="mb-3 w-25"><InputText @bind-Value="Model!.Name" /><ValidationMessage For="() => Model!.Name" /></div><div class="mb-3 w-25"><InputNumber @bind-Value="Model!.Age" /><ValidationMessage For="() => Model!.Age" /></div><div class="mb-3"><button type="submit">提交</button></div> </EditForm>@code {[SupplyParameterFromForm]public Student? Model { get; set; }protected override void OnInitialized(){Model ??= new();}void Submit(){Console.WriteLine(Model!.Name);}public class Student{[Required(ErrorMessage = "名字必须填写")]public string Name { get; set; } = null!;[Range(23, 30, ErrorMessage = "岁数只能是23~30")]public int Age { get; set; } = 23;} }
二、内置验证特性
ASP.NET Core 框架中提供了一批验证特性,通过验证特性可以为模型属性指定验证规则,这样使用验证特性可对模型类提供数据注解,好处是在没有运行应用程序的情况下,就能一目了然看到模型中每个属性应该处理什么样的数据,从而对模型对进行验证。
ASP.NET Core 中内置的验证特性
[Display]
:设置属性名称,一般用于验证信息,若没有使用[Display]
特性,则使用的是属性名
Name
:设置的属性名
[Required]
:验证属性不可为 null
[StringLength]
:验证字符串属性值是否未超过指定长度限制
[MaxLength]
:验证设定的最大长度
[MinLength]
:验证设定的最小长度
[Range]
:验证属性值是否在指定范围内
[Compare]
:验证模型中的两个属性是否匹配
[CreditCard]
:验证属性是否为信用卡格式
[EmailAddress]
:验证属性是否为电子邮件格式
[Phone]
:验证属性是否为电话号码格式
[Url]
:验证属性是否为 URL 格式
[RegularExpression]
:验证属性值是否与指定的正则表达式匹配
[FileExtensions]
:验证文件扩展名
[CustomValidation]
:用于自定义验证逻辑,可以进行个性化验证
1、非空验证
[Required]
:用于验证属性不可为 null
-
ErrorMessage
:验证失败时,显式的异常信息,可以使用{n}
占位符,其中{0}
表示Display
设置的名或默认属性名 -
AllowEmptyStrings
:是否允许空字符串,默认为false
-
示例
@page "/AnnotationsTest" @using System.ComponentModel.DataAnnotations<PageTitle>验证特性测试</PageTitle><EditForm Model="Model" OnValidSubmit="Submit"><DataAnnotationsValidator/><div class="mb-3 w-25"><label>姓名:<InputText @bind-Value="Model.Name"/><ValidationMessage For="()=> Model.Name"/></label></div><div class="w-25"><button class="btn btn-primary">提交</button></div> </EditForm>@code {[SupplyParameterFromForm]public Student Model { get; set; } = new();void Submit(){Console.WriteLine(Model?.Name);}public class Student{[Required(ErrorMessage = "{0}不可为空!")][Display(Name = "姓名")]public string Name { get; set; } = null!;} }
2、长度验证
[StringLength]
:用于验证字符串属性值是否未超过指定长度限制
MaximumLength
:最大长度MinimumLength
:最小长度ErrorMessage
:验证失败时显式的异常信息,可以使用{n}
占位符,其中{0}
表示Display
设置的名或默认属性名,{1}
表示[StringLength]
特性第一个属性值,即MaximumLength
的值,{2}
表示第二个属性值,即MinimumLength
的值
[MaxLength]
:验证设定的最大长度
[MinLength]
:验证设定的最小长度
-
示例
@page "/AnnotationsTest" @using System.ComponentModel.DataAnnotations<PageTitle>验证特性测试</PageTitle><EditForm Model="Model" OnValidSubmit="Submit"><DataAnnotationsValidator/><div class="mb-3 w-25"><label>地址:<InputText @bind-Value="Model.Address" /><ValidationMessage For="()=> Model.Address" /></label></div><div class="mb-3 w-25"><label>最大长度:<InputText @bind-Value="Model.MaxLength" /><ValidationMessage For="()=> Model.MaxLength" /></label></div><div class="mb-3 w-25"><label>最小长度:<InputText @bind-Value="Model.MinLength" /><ValidationMessage For="()=> Model.MinLength" /></label></div><div class="w-25"><button class="btn btn-primary">提交</button></div> </EditForm>@code {[SupplyParameterFromForm]public Student Model { get; set; } = new();void Submit(){//提交时的业务处理}public class Student{[Display(Name = "地址")][StringLength(5,MinimumLength=2,ErrorMessage="{0}的长度要在{2}~{1}之间")]public string? Address { get; set; }[Display(Name = "最大长度")][MaxLength(5, ErrorMessage = "{0}的最大长度为{1}")]public string? MaxLength { get; set; }[Display(Name = "最小长度")][MinLength(2, ErrorMessage = "{0}的最小长度为{1}")]public string? MinLength { get; set; }} }
3、正则验证
[RegularExpression]
:验证属性值是否与指定的正则表达式匹配
-
Pattern
:正则校验规则 -
ErrorMessage
:验证失败时显式的异常信息,可以使用{n}
占位符,其中{0}
表示Display
设置的名或默认属性名,{1}
表示[RegularExpression]
特性第一个属性值,即Pattern
的值 -
示例
@page "/AnnotationsTest" @using System.ComponentModel.DataAnnotations<PageTitle>验证特性测试</PageTitle><EditForm Model="Model" OnValidSubmit="Submit"><DataAnnotationsValidator/><div class="mb-3 w-25"><label>正则:<InputText @bind-Value="Model.Regex" /><ValidationMessage For="()=> Model.Regex" /></label></div><div class="w-25"><button class="btn btn-primary">提交</button></div> </EditForm>@code {[SupplyParameterFromForm]public Student Model { get; set; } = new();void Submit(){Console.WriteLine(Model?.Regex);}public class Student{[Display(Name = "正则数据")][RegularExpression("^[0-9]*[1-9][0-9]*$",ErrorMessage = "{0}不符合正则规则")] //正则public string? Regex { get; set; }} }
4、范围校验
[Range(minimum,maximum[,ErrorMessage])]
:用于指定数值类型的最小值和最大值之间的范围
-
minimum
:数值最小值 -
maximum
:数值最大值 -
ErrorMessage
:验证失败时显式的异常信息,可以使用{n}
占位符,其中{0}
表示Display
设置的名或默认属性名,{1}
表示[Range]
特性第一个属性值,即minimum
的值,{2}
表示第二个属性值,即maximum
的值 -
示例
@page "/AnnotationsTest" @using System.ComponentModel.DataAnnotations<PageTitle>验证特性测试</PageTitle><EditForm Model="Model" OnValidSubmit="Submit"><DataAnnotationsValidator/><div class="mb-3 w-25"><label>范围校验:<InputNumber @bind-Value="Model.Range"/><ValidationMessage For="()=> Model.Range"/></label></div><div class="w-25"><button class="btn btn-primary">提交</button></div> </EditForm>@code {[SupplyParameterFromForm]public Student Model { get; set; } = new();void Submit(){}public class Student{[Range(1, 5, ErrorMessage = "{0}最小值为{1},最大值为{2}")]public int Range { get; set; }} }
5、对比校验
[Compare(otherProperty[,ErrorMessage])]
:用于验证模型对象中的两个属性值是否相同,例如创建账号时的两次密码输入验证。
-
otherProperty
:要进行对比的属性名,可以借助nameof()
方法来获取 -
ErrorMessage
:验证失败时显式的异常信息 -
示例
@page "/AnnotationsTest" @using System.ComponentModel.DataAnnotations<PageTitle>验证特性测试</PageTitle><EditForm Model="Model" OnValidSubmit="Submit"><DataAnnotationsValidator/><div class="mb-3 w-25"><label>密码:<InputText @bind-Value="Model.Password" /></label></div><div class="mb-3 w-25"><label>密码校验:<InputText @bind-Value="Model.ConfirmPassword" /><ValidationMessage For="()=> Model.ConfirmPassword" /></label></div><div class="w-25"><button class="btn btn-primary">提交</button></div> </EditForm>@code {[SupplyParameterFromForm]public Student Model { get; set; } = new();void Submit(){Console.WriteLine(Model?.Password);}public class Student{public string? Password { get; set; }[Compare(nameof(Password), ErrorMessage = "密码不一致")]public string? ConfirmPassword { get; set; }} }
6、格式校验
[EmailAddress]
:验证<InputText>
组件中输入的内容是否为正确的电子邮箱地址格式
[Phone]
:用于验证国际标准手机号格式的正确性,如果要检验中国的11位手机号码,可以使用[RegularExpression]
特性进行验证
[CreditCard]
:用于验证信用卡号码的正确性
[Url]
:用于验证 URL 地址格式的正确性
-
ErrorMessage
:验证失败时,显式的异常信息,可以使用{0}
表示Display
设置的名或默认属性名 -
示例
@page "/AnnotationsTest" @using System.ComponentModel.DataAnnotations<PageTitle>验证特性测试</PageTitle><EditForm Model="Model" OnValidSubmit="Submit"><DataAnnotationsValidator /><div class="mb-3 w-25"><label>邮箱:<InputText @bind-Value="Model.Email" /><ValidationMessage For="()=> Model.Email" /></label></div><div class="mb-3 w-25"><label>手机号:<InputText @bind-Value="Model.Phone" /><ValidationMessage For="()=> Model.Phone" /></label></div><div class="mb-3 w-25"><label>信用卡:<InputText @bind-Value="Model.CreditCard" /><ValidationMessage For="()=> Model.CreditCard" /></label></div><div class="mb-3 w-25"><label>URL:<InputText @bind-Value="Model.URL" /><ValidationMessage For="()=> Model.URL" /></label></div><div class="w-25"><button class="btn btn-primary">提交</button></div> </EditForm>@code {[SupplyParameterFromForm]public Student Model { get; set; } = new();void Submit(){Console.WriteLine(Model.Phone);}public class Student{//邮箱地址[Display(Name = "邮箱")][EmailAddress(ErrorMessage = "请输入正确的邮箱")]public string? Email { get; set; }[Display(Name = "手机号")][Phone(ErrorMessage = "请输入正确的手机号码")]public string? Phone { get; set; }[Display(Name = "信用卡")][CreditCard(ErrorMessage = "请输入正确的信用卡号")]public string? CreditCard { get; set; }[Url(ErrorMessage = "请输入正确的URL地址")]public string? URL { get; set; }} }
7、文件扩展名校验
[FileExtensions]
:用于验证文件的扩展名。
-
Extensions
:设置要校验的文件扩展名,如果允许多个扩展名,可以使用逗号,
隔开 -
ErrorMessage
:验证失败时,显式的异常信息,可以使用{0}
表示Display
设置的名或默认属性名 -
示例
@page "/AnnotationsTest" @using System.ComponentModel.DataAnnotations<PageTitle>验证特性测试</PageTitle><EditForm Model="Model" OnValidSubmit="Submit"><DataAnnotationsValidator /><div class="mb-3 w-25"><label>文件名:<InputText @bind-Value="Model.FileName" /><ValidationMessage For="()=> Model.FileName" /></label></div><div class="w-25"><button class="btn btn-primary">提交</button></div> </EditForm>@code {[SupplyParameterFromForm]public FileModel Model { get; set; } = new();void Submit(){Console.WriteLine(Model.FileName);}public class FileModel{[FileExtensions(Extensions = "jpg,doc",ErrorMessage = "必须为jpg或doc文件")]public string? FileName { get; set; }} }
8、自定义校验
[CustomValidation(validatorType,methodName)]
:用于自定义验证逻辑,可以进行个性化验证
validatorType
:Type
类型,用于指定自定义校验方法所在的类型methodName
:string
类型,用于指定自定义的验证方法的名称
重点关注验证方法的几个要求:
-
必须为静态方法
-
必须返回
ValidationResult
类型,当验证方法验证成功时,返回ValidationResult.Success
值,验证失败时,通过返回new ValidationResult(errorMessage)
返回一条错误信息 -
接收两个参数,一个为目标校验值,一个为
ValidationContext
对象,可以通过该对象的ObjectInstance
属性获取目标校验属性的所在对象 -
示例
@page "/AnnotationsTest" @using System.ComponentModel.DataAnnotations<PageTitle>验证特性测试</PageTitle><EditForm Model="Model" OnValidSubmit="Submit"><DataAnnotationsValidator /><div class="mb-3 w-25"><label>姓名:<InputText @bind-Value="Model.Name" /><ValidationMessage For="()=> Model.Name" /></label></div><div class="mb-3 w-25"><label>岁数:<InputNumber @bind-Value="Model.Age" /><ValidationMessage For="()=> Model.Age" /></label></div><div class="w-25"><button class="btn btn-primary">提交</button></div> </EditForm>@code {[SupplyParameterFromForm]public CustomModel Model { get; set; } = new();void Submit(){Console.WriteLine(Model.Name);}public class CustomModel{[CustomValidation(typeof(CustomValidator), nameof(CustomValidator.ValidationName))]public string? Name { get; set; }[CustomValidation(typeof(CustomValidator), nameof(CustomValidator.ValidationAge))]public int Age { get; set; }}public class CustomValidator{public static ValidationResult? ValidationName(string data, ValidationContext validationContext){ //可以通过ValidationContext对象获取校验值,也可以直接使用第一个参数//string name = ((CustomModel)validationContext.ObjectInstance).Name!;if (data != null && data.Contains("ABC")){return ValidationResult.Success;}//返回错误信息return new ValidationResult("商品名称必须包含 ABC !");}public static ValidationResult? ValidationAge(int data, ValidationContext validationContext){//可以通过ValidationContext对象获取校验值,也可以直接使用第一个参数//int age = ((CustomModel)validationContext.ObjectInstance).Age!; if (data > 0 && data < 100){return ValidationResult.Success;}//返回错误信息return new ValidationResult("岁数必须在0~100之间");}} }
二、模型校验
有时候需要对多个属性进行综合性的验证,比如两个人属性的大小对比、开始和结束时间的先后约束、总数限制等等,因为依赖多个属性共同完成的校验,无法通过单个特性来实现,此时就需要用到IValidatableObject
接口。
IValidatableObject
是 .NET 中的一个接口,用于实现对象级别的自定义验证逻辑。它的核心作用是允许开发者编写复杂的验证规则,尤其是那些涉及多个属性或需要动态判断的验证场景,而不仅仅依赖于单个属性的特性声明(如 [Required]
、[Range]
等)。
1、定义模型校验
表单模型通过继承IValidatableObject
接口并实现Validate
方法即可定义模型校验的规则。在表单进行提交时,会自动进行模型校验。
-
示例
public class Order : IValidatableObject {public DateTime StartDate { get; set; }public DateTime EndDate { get; set; }public int Quantity { get; set; }public IEnumerable<ValidationResult> Validate(ValidationContext validationContext){// 规则1:结束时间必须晚于开始时间if (EndDate < StartDate){yield return new ValidationResult("结束时间必须晚于开始时间");}// 规则2:数量不能超过 100(假设需要动态判断)if (Quantity > 100){yield return new ValidationResult("数量不能超过 100", new[] { nameof(Quantity) });}} }
2、模型校验
模型校验的错误信息可以通过<ValidationSummary/>
组件展示,如果只希望展示模型校验的异常信息,只需要通过Model
属性绑定到指定的表单模型对象上即可。
-
示例
<EditForm Model="Input" method="post" OnValidSubmit="LoginUser" Enhance FormName="Login"><DataAnnotationsValidator /><ValidationSummary Model="@Input"/>...... </EditForm >
自定义表单验证(ValidationMessageStore)
一、表单验证的过程控制
Blazor中,使用EditForm
组件时,可以使用EditContext
和ValidationMessageStore
对象来控制表单的验证过程。
1、ValidationMessageStore
在 Blazor 中,ValidationMessageStore
是一个用于管理和存储验证错误消息的服务。它提供了一种集中管理和显示验证错误消息的机制,可以方便地在整个应用程序中使用。
构造方法
ValidationMessageStore(EditContext)
:设置对应编辑上下文对象的ValidationMessageStore
实例。
常用属性
this[Expression<Func<Object>>]
:索引器,获取指定字段在此ValidationMessageStore
中的验证消息。
常用方法
Add(Expression<Func<Object>>, String)
:为指定字段添加验证信息。
- 第一个参数中,是一个返回需要设置验证信息的字段的Lamda表达式
Add(Expression<Func<Object>>, IEnumerable<String>)
:为指定字段添加多条验证信息。
Clear()
:清除所有的消息。
Clear(Expression<Func<Object>>)
:消除指定字段的消息。
2、EditContext
在表单的验证过程中,EditContext
作为上下文对象,起到了承上启下的作用。
常用属性
Model
:object
属性,获取当前EditContext
对象所关联的表单模型对象。
ShouldUseFieldIdentifiers
:是否对表单模型字段使用唯一标识,可以确保在验证错误消息中能够准确地指示出错的字段,特别是在处理复杂表单时非常有用,默认情况下为true
。如果希望简化验证错误消息,可以将该属性设置为false
。
常用方法
bool Validate()
:表单验证是否通过。
FieldIdentifier Field(string fieldName)
:获取对应字段的可编辑唯一标识对象。
IEnumerable<string> GetValidationMessages([Expression<Func<object>> accessor/FieldIdentifier fieldIdentifier])
:获取全部或指定字段的验证错误信息,当表单字段验证失败时,Blazor会将相应的错误消息存储在EditContext
对象中,可以通过此方法获取,以便在UI中进行自定义展示。
bool IsModified([Expression<Func<object>> accessor/FieldIdentifier fieldIdentifier])
:任意字段或指定字段是否已被改变。
bool IsValid(Expression<Func<object>> accessor/FieldIdentifier fieldIdentifier)
:指定字段是否验证通过。
NotifyValidationStateChanged()
:当字段的验证状态发生变化时,可以调用此方法来通知Blazor组件进行重新渲染。
常用事件
OnValidationRequested(object? sender,ValidationRequestedEventArgs args)
:在EditForm
表单发起验证时会触发,可以在事件处理函数中进行验证处理。
OnFieldChanged(object? sender, FieldChangedEventArgs args)
:当字段内容发生变化时触发。
OnValidationStateChanged(object? sender, TEventArgs e)
:在表单验证状态发生变化时触发,例如字段的验证结果从有效变为无效或从无效变为有效时触发。
3、综合示例
-
示例
@page "/starship-8" @rendermode InteractiveServer @implements IDisposable @inject ILogger<Starship8> Logger<h2>Holodeck Configuration</h2><EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship8"><div><label><InputCheckbox @bind-Value="Model!.Subsystem1" />Safety Subsystem</label></div><div><label><InputCheckbox @bind-Value="Model!.Subsystem2" />Emergency Shutdown Subsystem</label></div><div><ValidationMessage For="() => Model!.Options"/></div><div><button type="submit">Update</button></div> </EditForm>@code {private EditContext? editContext;[SupplyParameterFromForm]public Holodeck? Model { get; set; }private ValidationMessageStore? messageStore;protected override void OnInitialized(){Model ??= new();editContext = new(Model);editContext.OnValidationRequested += HandleValidationRequested;messageStore = new(editContext);}private void HandleValidationRequested(object? sender,ValidationRequestedEventArgs args){//清除上一次遗留的验证结果信息messageStore?.Clear();//自定义验证逻辑if (!Model!.Options){messageStore?.Add(() => Model.Options, "Select at least one.");}}private void Submit(){Logger.LogInformation("Submit called: Processing the form");}public class Holodeck{public bool Subsystem1 { get; set; }public bool Subsystem2 { get; set; }public bool Options => Subsystem1 || Subsystem2;}public void Dispose(){if (editContext is not null){editContext.OnValidationRequested -= HandleValidationRequested;}} }
二、自定义验证器组件
Blazor 框架提供了 DataAnnotationsValidator
验证器组件,用于在EditForm
表单中开启特性验证。
如果对表单中的验证逻辑、顺序有自定义的需求,可以通过继承ComponentBase
来创建自定义的验证器组件。
- 大多数情况下,可以使用自定义的验证特性来代替自定义验证器组件。
继承ComponentBase
创建自定义验证器组件时,可以尝试实现如下内容:
-
表单的
EditContext
是组件的级联参数,可以在自定义验证器组件中接收该对象 -
初始化验证器组件时,创建一个基于
EditContext
对象的新的ValidationMessageStore
来维护当前表单的错误列表 -
定义
DisplayErrors(Dictionary<string, List<string>> errors)
方法,当开发人员调用此方法时,将验证信息设置到ValidationMessageStore
对象中。 -
发生以下任一情况时,将清除消息:
- 引发
EditContext
的OnValidationRequested
事件时,将ValidationMessageStore
对象的所有异常消息清除 - 引发
OnFieldChanged
事件时,将验证失败的字段清除 ClearErrors
方法由开发人员代码调用。 所有错误都将被清除。
- 引发
-
CustomValidation.cs
public class CustomValidation : 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();} }
-
Starship9.razor
@page "/starship-9" @inject ILogger<Starship9> Logger<h1>Starfleet Starship Database</h1><h2>New Ship Entry Form</h2><EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship9"><CustomValidation @ref="customValidation"/><ValidationSummary/><div><label>Primary Classification:<InputSelect @bind-Value="Model!.Classification"><option value="">Select classification ...</option><option checked="@(Model!.Classification == "Exploration")" value="Exploration">Exploration</option><option checked="@(Model!.Classification == "Diplomacy")" value="Diplomacy">Diplomacy</option><option checked="@(Model!.Classification == "Defense")" value="Defense">Defense</option></InputSelect></label></div><div><label>Description (optional):<InputTextArea @bind-Value="Model!.Description"/></label></div><div><button type="submit">Submit</button></div> </EditForm>@code {private CustomValidation? customValidation;[SupplyParameterFromForm]public Starship? Model { get; set; }protected override void OnInitialized() =>Model ??= new() { ProductionDate = DateTime.UtcNow };private void Submit(){customValidation?.ClearErrors();var errors = new Dictionary<string, List<string>>();if (Model!.Classification == "Defense" && string.IsNullOrEmpty(Model.Description)){errors.Add(nameof(Model.Description),new() { "For a 'Defense' ship classification, " + "'Description' is required." });}if (errors.Any()){customValidation?.DisplayErrors(errors);}else{Logger.LogInformation("Submit called: Processing the form");}} }
三、自定义验证特性
在实际项目开发过程中,大多数情况下,使用自定义验证特性就可以满足表单上的验证需求,并且自定义验证特性的复用性更强一些,其实现过程具体如下:
-
继承
ValidationAttribute
-
重写
IsValid
方法,返回验证结果 -
示例
public class ClassicMovieAttribute : ValidationAttribute {public ClassicMovieAttribute(int year)=> Year = year;public int Year { get; }//返回验证信息public string GetErrorMessage() =>$"Classic movies must have a release year no later than {Year}.";protected override ValidationResult? IsValid(object? value, ValidationContext validationContext){var movie = (Movie)validationContext.ObjectInstance;var releaseYear = ((DateTime)value!).Year;if (movie.Genre == Genre.Classic && releaseYear > Year){return new ValidationResult(GetErrorMessage());}return ValidationResult.Success;} }
IsValid
方法可以接收字段值以及一个ValidationContext
参数。ValidationContext
为验证的上下文对象,可以从中取得一些验证相关的信息。此外还可以通过ValidationContext
对象获取IOC容器中服务。
-
示例
public class SaladChefValidatorAttribute : ValidationAttribute {protected override ValidationResult? IsValid(object? value,ValidationContext validationContext){//这里假定SaladChef已经在Program.cs中注册了服务,其SaladToppers属性为一个字符串数组var saladChef = validationContext.GetRequiredService<SaladChef>();if (saladChef.SaladToppers.Contains(value?.ToString())){return ValidationResult.Success;}return new ValidationResult("Is that a Vulcan salad topper?! " +"The following toppers are available for a Ten Forward salad: " +string.Join(", ", saladChef.SaladToppers));} }
四、自定义CSS
默认情况下,Blazor框架在wwwroot/css/app.css
或 wwwroot/css/site.css
中设置了表单验证失败信息的基本样式
实现步骤
在wwwroot/css/app.css
或 wwwroot/css/site.css
中添加自定义的验证失败样式
-
示例
.validField {border-color: lawngreen; }.invalidField {background-color: tomato; }
创建自定义的样式提供类
-
继承
FieldCssClassProvider
-
重写
GetFieldCssClass
方法,返回对应CSS的class名称 -
CustomFieldClassProvider.cs
using Microsoft.AspNetCore.Components.Forms;public class CustomFieldClassProvider : FieldCssClassProvider {public override string GetFieldCssClass(EditContext editContext, in FieldIdentifier fieldIdentifier){var isValid = editContext.IsValid(fieldIdentifier);return isValid ? "validField" : "invalidField";} }
设置表单的验证演示提供对象
-
调用表单的
EditContext
实例的SetFieldCssClassProvider
方法,将CustomFieldClassProvider
设置为表单的验证样式提供对象 -
Starship13.razor
@page "/starship-13" @using System.ComponentModel.DataAnnotations @inject ILogger<Starship13> Logger<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship13"><DataAnnotationsValidator /><ValidationSummary /><div><label>Identifier: <InputText @bind-Value="Model!.Id" /></label></div><div><button type="submit">Submit</button></div> </EditForm>@code {private EditContext? editContext;[SupplyParameterFromForm]public Starship? Model { get; set; }protected override void OnInitialized(){Model ??= new();editContext = new(Model);editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());}private void Submit(){Logger.LogInformation("Submit called: Processing the form");}public class Starship{[Required][StringLength(10, ErrorMessage = "Id is too long.")]public string? Id { get; set; }} }
上面的示例中,是对表单模型的所有字段都生效的,如果只希望对指定的一些字段使用自定义样式,其他字段继续使用默认样式的话,就需要在GetFieldCssClass
方法中,根据需求返回对应的CSS类名
-
CustomFieldClassProvider.cs
using Microsoft.AspNetCore.Components.Forms;public class CustomFieldClassProvider : FieldCssClassProvider {public override string GetFieldCssClass(EditContext editContext,in FieldIdentifier fieldIdentifier){var isValid = editContext.IsValid(fieldIdentifier);if (fieldIdentifier.FieldName == "Name"){return isValid ? "validField" : "invalidField";}else{if (editContext.IsModified(fieldIdentifier)){return isValid ? "modified valid" : "modified invalid";}else{return isValid ? "valid" : "invalid";}}} }
表单模型的嵌套验证
虽然Blazor框架提供了DataAnnotationsValidator
来验证表单,但是DataAnnotationsValidator
仅验证绑定到表单的模型的基础类型的直接成员,并不能验证表单模型中的复杂类型(引用类型)属性。
如果想要验证绑定模型的整个对象图(包括模型中的复杂类型),可以尝试使用Microsoft.AspNetCore.Components.DataAnnotations.Validation
包提供的 ObjectGraphDataAnnotationsValidator
组件
Microsoft.AspNetCore.Components.DataAnnotations.Validation
可以从Nuget中下载
Microsoft.AspNetCore.Components.DataAnnotations.Validation
包据官方介绍,处于试验阶段,不知道有啥坑等着,能不用就先别用。
实现步骤
先创建一个表单模型类
-
示例
public class ShipDescription {[Required][StringLength(40, ErrorMessage = "Description too long (40 char).")]public string? ShortDescription { get; set; }[Required][StringLength(240, ErrorMessage = "Description too long (240 char).")]public string? LongDescription { get; set; } }
在模型中的复杂类型属性上,使用[ValidateComplexType]
特性
-
示例
public class Starship {...[ValidateComplexType]public ShipDescription ShipDescription { get; set; } = new();... }
在EditForm
表单中,使用<ObjectGraphDataAnnotationsValidator>
组件
-
示例
<EditForm ...><ObjectGraphDataAnnotationsValidator />... </EditForm>
相关文章:
第四章 表单(3)- 表单验证
在Blazor中,表单的验证可以通过两种方式实现,一种是使用Blazor所提供表单验证特性,另一种是使用ValidationMessageStore进行验证。 表单验证的基础使用(内置特性) 一、内置特性表单验证的开启 Blazor中,使用表单组件<EditFo…...
手撕AVL树
引入:为何要有AVL树,二次搜索树有什么不足? 二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此产生了AVL树,…...
Linux驱动开发练习案例
1 开发目标 1.1 架构图 操作系统:基于Linux5.10.10源码和STM32MP157开发板,完成tf-a(FSBL)、u-boot(SSBL)、uImage、dtbs的裁剪; 驱动层:为每个外设配置DTS并且单独封装外设驱动模块。其中电压ADC测试,采用linux内核…...
Redis 下载 — Ubuntu22.04稳定版,配置
官方文档 : https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/ Nano学习 : 【Linux环境下最先应该掌握的文本编辑器nano】https://www.bilibili.com/video/BV1p8411z7dJ?vd_source5ce003da2a16f44ea73ec9bbc30389e4 Redis配置…...
有没有可以帮助理解高数的视频或者书籍资料?
高数的学习是一个入门很高,但是一旦入门之后,就会变得比较简单的科目。 可是,我们应该怎么入门高数呢?在当年刚开始学习高数的时候,我也有过这样的困惑。 但是,后来我发现,我总是可以在经历一…...
了解拦截器
目录 什么是拦截器 拦截器的基本使用 拦截器的使用步骤 拦截器路径设置 拦截器执行流程 一、什么是拦截器 拦截器是Spring框架提供的核心功能之一,主要用来拦截用户的请求,在指定方法前后,根据业务需要执行预先设定的代码。 开发人员可以…...
Linux / Windows 下 Mamba / Vim / Vmamba 安装教程及安装包索引
目录 背景0. 前期环境查询/需求分析1. Linux 平台1.1 Mamba1.2 Vim1.3 Vmamba 2. Windows 平台2.1 Mamba2.1.1 Mamba 12.1.2 Mamba 2- 治标不治本- 终极版- 高算力版 2.2 Vim- 治标不治本- 终极版- 高算力版 2.3 Vmamba- 治标不治本- 终极版- 高算力版 3. Linux / Windows 双平…...
prism WPF 对话框
项目结构 1.创建对话框 用户控件 Views \ DialogView.xaml <UserControl x:Class"PrismWpfApp.Views.DialogView"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"…...
eventEmitter实现
没有做任何异常处理,简单模拟实现 事件对象的每一个事件都对应一个数组 /*__events {"事件1":[cb1,cb2],"事件2":[cb3,cb4],"事件3":[...],"事件4":[...],};*/class E{__events {};constructor(){}//注册监听回调on(type , callbac…...
Koordinator-NodeInfoCollector
Run 每秒执行一次 func (n *nodeInfoCollector) Run(stopCh <-chan struct{}) {go wait.Until(n.collectNodeInfo, n.collectInterval, stopCh) }collectNodeInfo() 采集node cpu信息采集node numa信息func (n *nodeInfoCollector) collectNodeInfo() {started := time.No…...
洛谷题单3-P5724 【深基4.习5】求极差 最大跨度值 最大值和最小值的差-python-流程图重构
题目描述 给出 n n n 和 n n n 个整数 a i a_i ai,求这 n n n 个整数中的极差是什么。极差的意思是一组数中的最大值减去最小值的差。 输入格式 第一行输入一个正整数 n n n,表示整数个数。 第二行输入 n n n 个整数 a 1 , a 2 … a n a_1,…...
SignalR给特定User发送消息
1、背景 官网上SignalR的demo很详细,但是有个特别的问题,就是没有详细阐述如何给指定的用户发送消息。 2、解决思路 网上整体解决思路有三个: 1、最简单的方案,客户端连接SignalR的Hub时,只是简单的连接,…...
新浪财经股票每天10点自动爬取
老规矩还是先分好三步,获取数据,解析数据,存储数据 因为股票是实时的,所以要加个cookie值,最好分线程或者爬取数据时等待爬取,不然会封ip 废话不多数,直接上代码 import matplotlib import r…...
【CSP】202403-1词频统计
文章目录 算法思路1. 数据结构选择2. 输入处理3. 统计出现的文章数4. 输出结果 代码示例代码优化 样例输入 4 3 5 1 2 3 2 1 1 1 3 2 2 2 2 3 2样例输出 2 3 3 6 2 2算法思路 1. 数据结构选择 vector<int>:用于存储每篇文章的单词列表(可能包含…...
CentOs系统部署DNS服务
1. 安装 Bind 软件包 首先需要安装bind以及相关的工具包,在终端中执行以下命令: bash sudo yum install bind bind-utils -y2. 配置主配置文件 Bind 的主配置文件是/etc/named.conf,你可以使用文本编辑器(如vim)打开…...
LintCode第974题-求矩阵各节点的最短路径(以0为标准)
描述 给定一个由0和1组成的矩阵,求每个单元格最近的0的距离。 两个相邻细胞之间的距离是1。 给定矩阵的元素数不超过10,000。 在给定的矩阵中至少有一个0。 单元格在四个方向上相邻:上,下,左和右。 样例 例1: 输入: [[0,0,0],[0,0,0],[0…...
吴恩达深度学习复盘(6)神经网络的矢量化原理
矢量化基础是线性运算,这里先简单复习一下。线性基本运算基本没什么,大量使用的有点乘和叉乘。 基本例子 1. 矩阵的基本概念 - 矩阵可以看作是一个块或者二维数组,这是对矩阵的一个在计算机计算的直观描述。 2. 向量的点积(内积…...
ISIS多区域配置
一、什么是ISIS多区域 ISIS(Intermediate System to Intermediate System)多区域是指网络被划分为多个逻辑区域(Areas),不同区域之间通过特定的ISIS路由器(Level-1-2)进行路由交互。多区域设计提…...
The emulator process for AVD xxx has terminated
问题描述 离线环境下部署Android虚拟机,启动时报错The emulator process for AVD xxx has terminated,其中xxx为虚拟机名称。 解决过程 可先在C:\Users\admin\AppData\Local\Google\AndroidStudio2024.3\log目录下找到idea.log文件,其中记录…...
Haskell语言的区块链扩展性
Haskell语言的区块链扩展性研究 引言 区块链技术近年来在金融、供应链、物联网等多个领域取得了显著的进展。作为一种分布式账本技术,区块链的核心在于其去中心化、不可篡改和透明性。然而,随着应用的不断深入,区块链面临着可扩展性、性能、…...
第11/100节:三点估算
第11/100节:三点估算 三、完成某信息系统集成项目中的一个最基本的工作单元 A 所需的时间,乐观的估计需 8 天,悲观的估计需 38天,最可能的估计需 20 天,按照三点估算方法进行估算,项目的工期应该为…...
Tourists
一道圆方树恶心题,*3200,不知道为什么不评黑。 这道题很容易直接想到圆方树:因为两个操作如果在树上,都需要树链剖分 线段树维护。而将这么一个普通图转化为一棵树,也就只有圆方树这种形式了。 于是就可以综合使用圆…...
【动态规划】深入动态规划:连续子结构的算法剖析
文章目录 前言例题一、最大子数组和二、环形子数组的最大和三、 乘积最大子数组四、乘积为正数的最长子数组五、等差数列划分六、最长湍流子数组七、单词拆分八、环绕字符串中唯一的子字符串 结语 前言 什么是是动态规划连续子数组、子串系列算法问题? 连续子数组问题通常聚焦…...
结肠镜3D视频数据集-C3VD论文中文版
文章目录 标题作者摘要一、介绍1.1. 相关工作1.1.1. 内镜重建数据集1.1.2. 注册真实和虚拟内窥镜图像1.1.3. 2D-3D注册1.2. 贡献 二、方法2.1. 幻影模型生产2.2. 数据采集2.3. 注册流程概述2.3.1. 数据预处理2.3.2. 目标深度估计2.3.3. 渲染深度帧2.3.4. 边缘损失和优化 2.4. 模…...
封装自己的api签名sdk
api平台接口调用,需要通过签名去核对是不是有效的用户,,一般会给两个key,acceeKey 和 secretKey,第一个相当于用户名,第二个相当于密钥,,,前端通过一定的算法,࿰…...
ASP.NET Core Web API 中 HTTP状态码的分类及对应的返回方法
文章目录 前言一、HTTP状态码分类及常用方法二、具体返回方法示例1) 2xx 成功类2)4xx 客户端错误3)5xx 服务器错误4)其他特殊状态码 三、高级返回方式1)使用 IActionResult 与 ActionResult<T>2)统一…...
函数和模式化——python
一、模块和包 将一段代码保存为应该扩展名为.py 的文件,该文件就是模块。Python中的模块分为三种,分别为:内置模块、第三方模块和自定义模块。 内置模块和第三方模块又称为库内置模块,有 python 解释器自带,不用单独安…...
LeetCode 1817 查找用户活跃分钟数
深入剖析 LeetCode 用户活跃分钟数统计问题 一、题目详情 给定用户在 LeetCode 的操作日志,日志以二维整数数组logs表示,其中每个logs[i][IDi, timei],意味着 ID 为IDi的用户在timei分钟时执行了某个操作。多个用户能够同时执行操作&#x…...
matlab从pytorch中导入LeNet-5网络框架
文章目录 一、Pytorch的LeNet-5网络准备二、保存用于导入matlab的model三、导入matlab四、用matlab训练这个导入的网络 这里演示从pytorch的LeNet-5网络导入到matlab中进行训练用。 一、Pytorch的LeNet-5网络准备 根据LeNet-5的结构图,我们可以写如下结构 import…...
网络:华为HCIA学习笔记:ICMP协议
ICMP(Internet Control Message Protocol)Internet控制消息协议 前言ICMPICMP重定向ICMP差错监测ICMP错误报告ICMP数据包格式ICMP消息类型和编码类型ICMP应用-PingICMP应用-Tracert 总结 前言 Internet控制消息协议ICMP (Internet Control Message Prot…...
导出为更清楚/高质量的图片(.png) | 在Word里面的visio图)
Visio | 将(.vsdx)导出为更清楚/高质量的图片(.png) | 在Word里面的visio图
此时大家在用Visio画完图直接复制到word里面后,如果后期需要重新保存高清图片,但是此时图片在word,是不是很多人会选择直接crtlA截图复制,这样出来的图又不清晰又小,完全不符合你导的审美,接下来跟着我&…...
算法设计学习8
实验目的及要求: 通过深入学习树(Tree)和二叉树(Binary Tree)这两种重要的数据结构,掌握它们的基本概念、性质和操作,提高对树形结构的理解和应用能力。通过本实验,学生将深化对树和…...
Dive into Deep Learning - 2.4. Calculus (微积分)
Dive into Deep Learning - 2.4. Calculus {微积分} 1. Derivatives and Differentiation (导数和微分)1.1. Visualization Utilities 2. Chain Rule (链式法则)3. DiscussionReferences 2.4. Calculus https://d2l.ai/chapter_preliminaries/calculus.html For a long time, …...
kotlin中const 和val的区别
在 Kotlin 中,const 和 val 都是用来声明常量的,但它们的使用场景和功能有所不同: 1. val: val 用于声明只读变量,也就是不可修改的变量(类似于 Java 中的 final 变量)。它可以是任何类型,包括…...
Webpack中loader的作用。
文章目录 前言1. 处理样式文件2. 处理 JavaScript 文件3. 处理其他文件总结 前言 在 Webpack 中,Loader 是用于对模块的源代码进行转换的工具,它能够将不同类型的文件(如 CSS、图片、字体、TypeScript 等)转换为有效的 JavaScrip…...
C++ 极简常用内容
C 极简常用内容 1. 类与对象 定义:封装数据(成员变量)和行为(成员函数)的自定义类型。 Demo: class Car { public:string brand;void drive() { cout << brand << " is moving." …...
如何在windows 环境、且没有显卡的情况下用python跑通从ModelScope下载的大模型的调用
文章目录 背景介绍源代码:安装调试过程1.设置第三方镜像源2.预先安装:3.在python中创建代码:4.最终修改程序,将device_map从“cuda”改成“auto”,大模型调用1.5B(1___5B)的5.最终跑出结果解释:示例&#x…...
MINIQMT学习课程Day10
开始获取股票数据课程的学习: 获取qmt账号的持仓情况后,我们进入下一步,如何获得当前账号的委托状况 还是之前的步骤,打开qmt,选择独立交易, 之后使用pycharm,编写py文件 导入包:…...
如何彻底关闭Windows 10中的Xbox游戏栏
一、打工人的困扰:不速之客“Game Bar” 在日常工作中,许多使用Windows 10的用户常常被一个不起眼却频频打扰的系统功能困扰,那就是“Xbox游戏栏”(Game Bar)。当你正在专注处理紧急的Excel表格或准备PPT汇报…...
26考研资料分享考研资料合集 百度网盘(仅供参考学习)
考研资料分享考研资料合集 百度网盘(仅供参考学习) 通过网盘分享的文件:2026考研英语数学政治最新等3个文件 链接1: https://pan.baidu.com/s/1JXBI9ROng4KAWHoaUHpkug?pwdjydb 提取码: jydb 链接2:https://pan.baidu.com/s/1a…...
59.基于ssm和vue学生考试成绩管理系统
目录 1.系统的受众说明 2.系统关键技术 2.1 java技术 2.2 MYSQL数据库 2.3 B/S结构 3.系统分析 3.1 可行性分析 3.1.1 技术可行性 3.1.2经济可行性 3.2 系统性能分析 3.3 系统功能分析 3.5系统流程分析 3.5.1登录流程 3.5.2注册流程 3.5.3添加信息流程 3.5.4删…...
常见的ETL工具分类整理
一、开源ETL工具 Kettle(Pentaho Data Integration)--Spoon 设计及架构:面向数据仓库建模的传统ETL工具。使用方式:C/S客户端模式,开发和生产环境需要独立部署,任务编写、调试、修改都在本地。底层架构…...
【leetcode100】数组中的第K个最大元素
1、题目描述 给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。 请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。 你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1: 输入: [3,2,…...
markdown语法学习
三化markdown语法 研究对象系统化全局化结构化markdown语法富文本字体样式*斜体*,主题样式#,表格样式||,代码块样式,待办样式- [ ]样式之间互相协作,互不冲突 待办 斜体 加粗 标题 删除线 public class{ //代码块 …...
Linux_4
开始学习ssh工具 在做开发的时候,肯定不止一台服务器,那么假设每台服务器都是Linux服务器,要在服务器上操作就需要登入终端,即Terminal。ssh的作用就是可以通过一个服务器登陆上其他的服务器。 登陆到哪个服务器看到的就是哪个服务器的终端terminal。 ssh登陆 ssh user@…...
Netty——连接超时 与 断开重连
文章目录 1. 处理连接超时和断开重连的原因2. 处理连接超时和断开重连的方法2.1 处理连接超时2.1.1 步骤一:配置连接超时时间2.1.2 步骤二:监听连接结果 2.2 处理断开重连2.2.1 步骤一:监听连接断开事件2.2.2 步骤二:实现重连逻辑…...
linux 进程/线程设置核亲和性
1,线程绑定内核 #include <pthread.h> #include <stdio.h> #include <stdlib.h> // 定义一个函数,用于设置线程的CPU亲和性 void set_thread_affinity(pthread_t thread, int cpu_id) { cpu_set_t cpuset; int s; // 清空CPU集…...
前端页面鼠标移动监控(鼠标运动、鼠标监控)鼠标节流处理、throttle、限制触发频率(setTimeout、clearInterval)
文章目录 使用lodashjs库手动实现节流(通过判断之前设定的定时器setTimeout是否存在) 使用lodashjs库 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Com…...
【MySQL基础-21】MySQL事务机制详解:原理、实现与最佳实践
在现代数据库系统中,事务机制是确保数据一致性和完整性的核心技术。MySQL作为最流行的开源关系型数据库之一,其事务实现机制对于开发者而言至关重要。本文将深入探讨MySQL的事务机制,包括核心概念、实现原理、隔离级别以及实际应用中的最佳实…...
Transformer+BO-SVM时间序列预测(Matlab)
TransformerBO-SVM时间序列预测(Matlab) 目录 TransformerBO-SVM时间序列预测(Matlab)效果一览基本介绍程序设计参考资料 效果一览 基本介绍 普通的单变量时序已经用腻了,审稿人也看烦了,本期推出一期高创…...