FluentValidation 筆記
用於 .NET5。Blazor App - Form Validation.
安裝套件
/// 應用於 Blazor App
Install-Package Blazored.FluentValidation
/// 核心模組
Install-Package FluentValidation
/// 可註冊全部的 IValidator<T> 服務
Install-Package FluentValidation.DependencyInjectionExtensions
解析(resolve)取得欄位名稱
public class Startup
{
using System.ComponentModel.DataAnnotations;
public void ConfigureServices(IServiceCollection services)
{
///# for 多國語系(optional, used to support IStringLocalizer)
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.Configure<RequestLocalizationOptions>(options =>
{
var cultures = Configuration.GetSection("Cultures").GetChildren().ToDictionary(x => x.Key, x => x.Value);
var supoortedCultures = cultures.Keys.ToArray();
options.AddSupportedCultures(supoortedCultures);
options.AddSupportedUICultures(supoortedCultures);
options.SetDefaultCulture("zh-TW");
options.DefaultRequestCulture = new Microsoft.AspNetCore.Localization.RequestCulture("zh-TW");
});
///# for FluentValidator 註冊所有 IValidator<T>
var exeAsm = Assembly.GetExecutingAssembly();
services.AddValidatorsFromAssembly(exeAsm);
services.AddRazorPages();
....
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
/// 設定自 DisplayAttribute 取得欄位名稱
/// FluentValidation 的組態改到 Global 設定。
FluentValidation.ValidatorOptions.Global.DisplayNameResolver = (type, member, expression) =>
(member.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() as DisplayAttribute)?.GetName();
if (env.IsDevelopment())
....
}
}
Model範例
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using FluentValidation;
namespace BlazorServerApp.Data
{
public class Customer
{
[Display(Name = "識別碼")]
public int Id { get; set; }
[Display(Name = "姓氏")]
public string Surname { get; set; }
[Display(Name = "名字")]
public string Forename { get; set; }
[Display(Name = "折扣")]
public decimal Discount { get; set; }
[Display(Name = "住址")]
public string Address { get; set; }
}
public class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
RuleFor(c => c.Id).NotNull();
// .WithMessage("姓氏就是不可以是空的!");
RuleFor(c => c.Surname).NotEmpty();
RuleFor(c => c.Forename).NotEmpty();
RuleFor(c => c.Address).NotEmpty().MinimumLength(10);
RuleFor(c => c.Address).EmailAddress();
// .WithMessage("當姓氏為money時折扣值需小於500哦。")
RuleFor(c => c.Discount).LessThan(500)
.When(SurnameIsMoney);
RuleFor(c => c.Discount).InclusiveBetween(1000, 9000)
.Unless(SurnameIsMoney);
When(SurnameIsMoney, () =>
{
RuleFor(c => c.Discount).LessThan(500).WithMessage("當姓氏為money,值需小於500哦。");
RuleFor(c => c.Discount).LessThan(500).WithMessage("當姓氏為money,值需小於500哦。");
RuleFor(c => c.Discount).LessThan(500).WithMessage("當姓氏為money,值需小於500哦。");
});
RuleFor(c => c.Discount)
.LessThan(500)
.DependentRules(() =>
{
RuleFor(c => c.Surname).Equal("money");
});
RuleForEach(c => c.itemList)....
// 補充 on 2023/12/01:
// 上班時間:必填且需符合時間格式。HH:mm
RuleFor(m => m.OnWorkTime)
.NotEmpty()
.Must(text => TimeSpan.TryParse(text, out TimeSpan result))
.WithMessage($"'上班時間' 時間格式不對。");
// 下班時間:空白或需符合時間格式。HH:mm
RuleFor(m => m.OffWorkTime)
.Must(offWorkTime => TimeSpan.TryParse(offWorkTime, out TimeSpan result))
.When(m => !string.IsNullOrWhiteSpace(m.OffWorkTime))
.WithMessage("'下班時間' 時間格式不對。")
.GreaterThan(m => m.OnWorkTime)
.When(m => m.OverNight != "Y") // 當沒有跨夜下班。
.WithMessage("'下班時間' 需大於 '上班時間'。");
}
bool SurnameIsMoney(Customer c) => c.Surname == "money";
}
}
應用範例
@using BlazorServerApp.Data
@using Serilog
@using Blazored.FluentValidation
@page "/fluent-validation-lab"
<h2>Fluent Validation Lab</h2>
<EditForm Model="formData" OnValidSubmit="@SubmitForm">
@* <DataAnnotationsValidator /> *@
<FluentValidationValidator />
<ValidationSummary />
<p>
<Label For="()=>formData.Id" />
<InputNumber TValue="int" @bind-Value="formData.Id" />
<ValidationMessage For="()=>formData.Id" />
</p>
<p>
<Label For="()=>formData.Surname" />
<InputText @bind-Value="formData.Surname" />
<ValidationMessage For="()=>formData.Surname" />
</p>
<p>
<Label For="()=>formData.Forename" />
<InputText @bind-Value="formData.Forename" />
<ValidationMessage For="()=>formData.Forename" />
</p>
<p>
<Label For="()=>formData.Discount" />
<InputNumber TValue="decimal" @bind-Value="formData.Discount" />
<ValidationMessage For="()=>formData.Discount" />
</p>
<p>
<Label For="()=>formData.Address" />
<InputTextArea @bind-Value="formData.Address" />
<ValidationMessage For="()=>formData.Address" />
</p>
<button type="submit" class="btn btn-primary">送出</button>
<button type="button" class="btn btn-secondary" @onclick="HandlePostForm">手動檢查</button>
</EditForm>
@code{
Customer formData = new Customer();
CustomerValidator validator = new CustomerValidator(); // 用於手動檢查
// 預設EditForm會自動整合 CustomerValidator,這裡是了手動再檢查一次。
private void SubmitForm()
{
// 自動檢查通過才會觸發此函式
//... proceed...
Log.Information("SubmitForm {@formData}", formData);
}
private void HandlePostForm()
{
// 手動再檢查一次
var vr = validator.Validate(formData);
if (!vr.IsValid)
{
foreach (var failure in vr.Errors)
{
Console.WriteLine("Property " + failure.PropertyName + " failed validation. Error was: " + failure.ErrorMessage);
}
}
//... proceed...
Log.Information("HandlePostForm {@formData}", formData);
}
}
專業應用:Dependency Injection & Localization
基本的應用不需要 DI 注入服務,有全球化(在地方)的應用就需要注入才能取得多國語言的資源。
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using FluentValidation;
using FluentValidation.Results;
using Microsoft.Extensions.Localization;
namespace BlazorServerApp.Data
{
public class Person
{
[Display(Name = "序號")]
public int Sn { get; set; }
[Display(Name = "姓氏")]
public string Surname { get; set; }
[Display(Name = "名字")]
public string Forename { get; set; }
}
public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator(IStringLocalizer<Person> localizer)
{
// 透過 IStringLocalizer 介面取得多國語言資源
RuleFor(x => x.Surname)
.NotNull()
.WithMessage(x => localizer["Surname is required"]);
// 透過 IStringLocalizer 介面取得多國語言資源
RuleFor(x => x.Forename)
.NotNull()
.WithMessage(x => localizer["Forename is required"]);
}
}
}
沒圖沒真象之 FluentValidation 透過IStringLocalizer
介面取得多國語言資源的設定紀錄。

FluentValidation 透過 DI 取得實作IValidator<T>
介面的 PersonValidator 服務。
@using BlazorServerApp.Data
@using Blazored.FluentValidation
@using FluentValidation
@page "/fluent-validation-lab"
<h2>Fluent Validation Lab</h2>
<EditForm Model="personData" OnValidSubmit="@SubmitPersonForm">
<FluentValidationValidator />
<ValidationSummary />
<p>
<Label For="() => personData.Surname" />
<InputText @bind-Value="personData.Surname" />
<ValidationMessage For="() => personData.Surname" />
</p>
<p>
<Label For="() => personData.Forename" />
<InputText @bind-Value="personData.Forename" />
<ValidationMessage For="() => personData.Forename" />
</p>
<button type="submit" class="btn btn-primary">送出</button>
<button type="button" @onclick="HandlePostPersonForm" class="btn btn-primary">手動檢查</button>
</EditForm>
<pre>@personVrJson</pre>
@code{
Person personData = new Person();
[Inject] IValidator<Person> personValidator { get; set; }
// 透過DI注入服務
string personVrJson = "";
private void SubmitPersonForm()
{
// 自動檢查通過
}
private void HandlePostPersonForm()
{
// 手動檢查
var vr = personValidator.Validate(personData);
personVrJson = Utils.JsonSerialize(vrP);
}
}
Last updated