客官,.NETCore无代码侵入的模型验证了解下
.NETCore下的模型驗(yàn)證相信絕大部分的.NET開(kāi)發(fā)者或多或少的都用過(guò),微軟官方提供的模型驗(yàn)證相關(guān)的類(lèi)位于System.ComponentModel.DataAnnotations命令空間下,在使用的時(shí)候只需要給屬性添加不同的特性即可實(shí)現(xiàn)對(duì)應(yīng)的模型驗(yàn)證。如下所示:
?
public class Movie {public int Id { get; set; }[Required][StringLength(100)]public string Title { get; set; } }在WebApi中,當(dāng)請(qǐng)求接口時(shí),程序會(huì)自動(dòng)對(duì)模型進(jìn)行驗(yàn)證,如無(wú)法驗(yàn)證通過(guò),則會(huì)直接終止后續(xù)的邏輯執(zhí)行,并響應(yīng)400狀態(tài)碼,響應(yīng)內(nèi)容如下所示:
{ "type": "https://tools.ietf.org/html/rfc7231#p-6.5.1", "title": "One or more validation errors occurred.", "status": 400, "traceId": "00-4b16460fc83d7b4daa4f10d939016982-f823eebede419a4a-00", "errors": { "aa": [ "The aa field is required." ] } }當(dāng)然,你也可以自定義響應(yīng)的內(nèi)容,這不是本文的重點(diǎn)。本文的重點(diǎn)是,.NETCore系統(tǒng)默認(rèn)的模型驗(yàn)證功能并不夠強(qiáng)大,僅支持在Controller的Action中使用,不支持非Controller中或者控制臺(tái)程序的驗(yàn)證,且代碼侵入性較強(qiáng)。
而FluentValidation(https://fluentvalidation.net/?)則是功能更為強(qiáng)大的模型驗(yàn)證框架,支持任何場(chǎng)景下的模型驗(yàn)證,且不侵入代碼。
下面就來(lái)和筆者一起了解下FluentValidation的用法。
?
接入
FluentValidation支持一下平臺(tái):
.NET 4.6.1+
.NET Core 2.0+
.NET Standard 2.0+
各個(gè)平臺(tái)的集成方式大同小異,本文僅講解.NETCore3.1的集成方式。
首先,使用NuGet安裝FluentValidation.AspNetCore依賴(lài)。
添加需要驗(yàn)證的模型類(lèi),如Student類(lèi),代碼如下:
public class Student {public int Id { get; set; }public int Age { get; set; }public string Name { get; set; } }然后創(chuàng)建類(lèi)StudentValidator,并集成類(lèi)AbstractValidator<Student>,代碼如下:
?
public class StudentValidator : AbstractValidator<Student> {public StudentValidator(){RuleFor(x => x.Age).InclusiveBetween(10, 50);RuleFor(x => x.Name).NotEmpty().MaximumLength(5);} }上述的驗(yàn)證類(lèi)中,要求Age大于10且小于50,Name不為空,且長(zhǎng)度小于5。
最后,還需要將驗(yàn)證類(lèi)注冊(cè)到服務(wù)中。修改Startup的ConfigureServices,部分代碼如下:
?
services.AddControllers().AddFluentValidation(conf =>{conf.RegisterValidatorsFromAssemblyContaining<StudentValidator>();conf.RunDefaultMvcValidationAfterFluentValidationExecutes = false;});上述代碼中,RegisterValidatorsFromAssemblyContaining方法的作用是掃描StudentValidator類(lèi)所在的程序集中的所有驗(yàn)證類(lèi),并注冊(cè)到服務(wù)中。
RunDefaultMvcValidationAfterFluentValidationExecutes為false時(shí),會(huì)屏蔽掉系統(tǒng)默認(rèn)的模型驗(yàn)證,如需兼容系統(tǒng)默認(rèn)的模型驗(yàn)證,將RunDefaultMvcValidationAfterFluentValidationExecutes的值改為true即可。此參數(shù)默認(rèn)為true。
下面在Controller中,添加一個(gè)Action,代碼如下:
?
[HttpPost] public IActionResult Add([FromBody] Student student) {return Ok(student); }打開(kāi)swagger,訪問(wèn)接口,響應(yīng)如下所示:
?
{"type": "https://tools.ietf.org/html/rfc7231#p-6.5.1","title": "One or more validation errors occurred.","status": 400,"traceId": "00-6331a76578228b4cb9044aa40f514bc9-89fd8547c1921340-00","errors": {"Age": ["'Age' 必須在 10 (包含)和 25 (包含)之間, 您輸入了 0。"],"Name": ["'Name' 必須小于或等于5個(gè)字符。您輸入了6個(gè)字符。"]} }至此,在 ASP.NET Core中集成FluentValidation就完成了。但到現(xiàn)在為止,這和系統(tǒng)默認(rèn)的模型驗(yàn)證并沒(méi)有區(qū)別。在文章的開(kāi)頭筆者也提到過(guò),FluentValidation不僅支持Controller中對(duì)模型進(jìn)行驗(yàn)證,下面的代碼就是非Controller場(chǎng)景下的驗(yàn)證。
?
public class DemoService {private readonly IValidator<Student> _studentValidator;public DemoService(IValidator<Student> studentValidator){_studentValidator = studentValidator;}public bool Run(Student student){var valid = _studentValidator.Validate(student);return valid.IsValid;} }在上述代碼中,通過(guò)構(gòu)造函數(shù)注入的方式,獲取到了IValidator<Student>實(shí)例,在Run方法中只需要調(diào)用Validate方法,參數(shù)是需要驗(yàn)證的對(duì)象,返回的對(duì)象就包含了驗(yàn)證的是否通過(guò)以及不通過(guò)時(shí),具體的錯(cuò)誤信息。
?
基礎(chǔ)用法
?
內(nèi)置規(guī)則
FluentValidation內(nèi)置了多個(gè)常用的驗(yàn)證器,下面簡(jiǎn)單介紹幾個(gè)特別常用或容易出錯(cuò)的驗(yàn)證器。
NotNull 和 NotEmpty
NotNull是確保指定的屬性不為null,NotEmpty則表示確保指定的屬性不為null、空字符串或空白(值類(lèi)型的默認(rèn)值,比如int類(lèi)型的默認(rèn)值為0),如果int類(lèi)型屬性設(shè)置NotEmpty驗(yàn)證器,則當(dāng)值為0時(shí),驗(yàn)證是無(wú)法通過(guò)的。
NotEqual 和 Equal
NotEqual 和 Equal分別是不相等和相等驗(yàn)證器,可與指定的值或者指定的屬性進(jìn)行比較。
MaximumLength、MinimumLength和Length
MaximumLength為最大長(zhǎng)度驗(yàn)證器,MinimumLength為最小長(zhǎng)度驗(yàn)證器,而Length則是二者的結(jié)合,需要注意的是,這三種驗(yàn)證器僅對(duì)字符串有效,且不會(huì)驗(yàn)證null,當(dāng)值為null時(shí),則不對(duì)長(zhǎng)度進(jìn)行驗(yàn)證,所以使用長(zhǎng)度驗(yàn)證器時(shí),建議結(jié)合NotNull一起使用。
?
LessThan、LessThanOrEqualTo、GreaterThan、GreaterThanOrEqualTo
上述的幾個(gè)驗(yàn)證器為比較驗(yàn)證器,僅適用于繼承IComparable接口的屬性,分別表示的是:小于、小于或等于、大于、大于或等于。
?
Matches
正則表達(dá)式驗(yàn)證器,用于確保指定的屬性與給定的正則表達(dá)式匹配。
ExclusiveBetween和InclusiveBetween
示例代碼如下:
RuleFor(x => x.Id).ExclusiveBetween(1,10); RuleFor(x => x.Id).InclusiveBetween(1,10);以上代碼均表示輸入的Id的值需要在1,10之間,而兩者的區(qū)別是,InclusiveBetween驗(yàn)證器是包含頭和尾的,而ExclusiveBetween是不包含的,例如當(dāng)Id值為1時(shí),ExclusiveBetween驗(yàn)證失敗,但I(xiàn)nclusiveBetween則驗(yàn)證成功。
覆蓋驗(yàn)證器默認(rèn)的錯(cuò)誤提示
在文章的開(kāi)頭提到了,當(dāng)驗(yàn)證Student的Age屬性不通過(guò)時(shí),提示信息是:'Age' 必須在 10 (包含)和 25 (包含)之間, 您輸入了 0。
這個(gè)提示信息對(duì)于開(kāi)發(fā)者來(lái)講,定位問(wèn)題已經(jīng)很清晰了,但如果要在WebApi中講驗(yàn)證的錯(cuò)誤信息返回給前端,那么這個(gè)提示就會(huì)被用戶(hù)看到,則此錯(cuò)誤信息就不太友好,FluentValidation提供了多種覆蓋錯(cuò)誤提示的方式,下面就來(lái)一起看下。
?
占位符
我們可以將驗(yàn)證Age的代碼改為如下所示:
?
RuleFor(x => x.Age).InclusiveBetween(10, 25).WithMessage("年齡必須在{From}到{To}之間");當(dāng)驗(yàn)證不通過(guò)時(shí),輸出的錯(cuò)誤信息則為:年齡必須在10到25之間。
程序自動(dòng)將{From}和{To}進(jìn)行了替換。每個(gè)驗(yàn)證器的占位符都不一樣,有關(guān)占位符的完整列表,請(qǐng)查看官方文檔?https://docs.fluentvalidation.net/en/latest/built-in-validators.html。
?
覆蓋屬性名稱(chēng)
此方法是將屬性的名稱(chēng)使用指定的字符串替換,如下所示:
?
RuleFor(x => x.Age).InclusiveBetween(10, 25).WithName("年齡");當(dāng)發(fā)生錯(cuò)誤時(shí),會(huì)自動(dòng)將系統(tǒng)默認(rèn)的錯(cuò)誤提示信息中的"Age"替換為"年齡"
默認(rèn)情況下,When或者Otherwise將應(yīng)用于鏈?zhǔn)秸{(diào)用的所有前置的驗(yàn)證器,如果只希望條件引用于前面的第一個(gè)驗(yàn)證器,則必須使用ApplyConditionTo.CurrentValidator顯示指定
?
RuleFor(x => x.Age).GreaterThan(10).LessThan(20).When(x => x.Sex == 2,ApplyConditionTo.CurrentValidator);上述的代碼,如果不加ApplyConditionTo.CurrentValidator,則當(dāng)Sex等于2時(shí),則要求Age大于10且小于20。而Sex不等于2時(shí),則不作任何驗(yàn)證。如果加上ApplyConditionTo.CurrentValidator,則Age大于10的驗(yàn)證跟Sex的值沒(méi)有任何關(guān)系了,程序會(huì)始終驗(yàn)證Age是否大于10
?
帶條件的驗(yàn)證規(guī)則
使用When方法可控制規(guī)則執(zhí)行的條件。例如,國(guó)家的法定結(jié)婚年齡為女性20歲,則驗(yàn)證年齡屬性時(shí),只有當(dāng)性別為女時(shí),才對(duì)年齡大于等于20進(jìn)行校驗(yàn)。
?
RuleFor(x => x.Age).GreaterThan(20).When(x => x.Sex == 2);相反的,Unless表示的是當(dāng)指定條件不滿(mǎn)足時(shí),才執(zhí)行校驗(yàn)。
RuleFor(x => x.Age).GreaterThan(20).Unless(x => x.Sex == 2);上述代碼表示當(dāng)Sex值不為2時(shí),校驗(yàn)Age是否大于等于20
如果需要為多個(gè)驗(yàn)證規(guī)則指定相同的條件,可以調(diào)用When的頂級(jí)方法,而不是在規(guī)則末尾調(diào)用When方法。
?
When(x => x.Sex == 2, () => {RuleFor(x => x.Name).Must(x => !x.EndsWith("國(guó)慶"));RuleFor(x => x.Age).LessThan(30); });上述代碼表示是,當(dāng)Sex等于2時(shí),Age需要小于30,并且名字不能以"國(guó)慶"結(jié)尾。
將Otherwise方法鏈接到When調(diào)用,表示W(wǎng)hen條件不滿(mǎn)足時(shí),執(zhí)行的驗(yàn)證規(guī)則。
?
When(x => x.Sex == 2, () => {RuleFor(x => x.Name).Must(x => x.EndsWith("國(guó)慶"));RuleFor(x => x.Age).LessThan(30); }).Otherwise(() => {RuleFor(x => x.Age).LessThan(50); });上述代碼中的Otherwise方法表示的是,當(dāng)Sex不等于2時(shí),則Age需要小于50
?
鏈?zhǔn)秸{(diào)用
當(dāng)一個(gè)屬性使用多個(gè)驗(yàn)證規(guī)則時(shí),可將多個(gè)驗(yàn)證器鏈接在一起,比如,Student類(lèi)的Name屬性不能為空,并且,長(zhǎng)度需要小于10,則對(duì)應(yīng)的代碼為:
?
public StudentValidator() {RuleFor(x =>x.Name).NotEmpty().MaximumLength(10); }CascadeMode
CascadeMode是一個(gè)枚舉類(lèi)型的屬性,有兩個(gè)選項(xiàng):Continue和Stop
如果設(shè)置為Stop,則檢測(cè)到失敗的驗(yàn)證,則立即終止,不會(huì)繼續(xù)執(zhí)行剩余屬性的驗(yàn)證。默認(rèn)值為Continue
CascadeMode = CascadeMode.Stop; RuleFor(x => x.Name).NotEmpty().MaximumLength(10); RuleFor(x => x.NickName).NotEmpty().MaximumLength(10);如上述代碼所示,當(dāng)Name值不滿(mǎn)足要求時(shí),則會(huì)停止對(duì)NickName的校驗(yàn)
依賴(lài)規(guī)則
默認(rèn)情況下,FluentValidation 中的所有規(guī)則都是獨(dú)立的,不能彼此影響。這是異步驗(yàn)證工作所必需的,也是必要的。但是,在某些情況下,您可能希望確保某些規(guī)則僅在另一個(gè)規(guī)則完成之后執(zhí)行。您可以使用DependentRules它來(lái)做到這一點(diǎn)。
比如,只有身高超過(guò)130的兒童,才需要驗(yàn)證是否購(gòu)票,則可以通過(guò)如下的代碼實(shí)現(xiàn):
RuleFor(x => x.Height).GreaterThan(130).DependentRules(() => {RuleFor(x => x.HasTicket).NotEmpty(); });?
高級(jí)用法
異步驗(yàn)證
在某些情況下,你可能希望定義異步規(guī)則,比如從數(shù)據(jù)庫(kù)或者外部api判斷。
?
public StudentValidator(IStudentService studentService) {_studentService = studentService;RuleFor(x => x.Name).MustAsync(async (name, token) => await _studentService.CheckExist(name)); }上述代碼中,通過(guò)一個(gè)異步方法的返回值驗(yàn)證Name屬性。
另外,如果在非Controller場(chǎng)景下使用,則必須調(diào)用ValidateAsync方法進(jìn)行驗(yàn)證。
轉(zhuǎn)換值
您可以在對(duì)屬性值執(zhí)行驗(yàn)證之前使用 Transform方法轉(zhuǎn)換屬性值。
RuleFor(x => x.Weight).Transform(x => int.TryParse(x, out int val)?(int?)val:null).GreaterThan(10);上述代碼先試圖將string類(lèi)型轉(zhuǎn)換成int類(lèi)型,如果轉(zhuǎn)換成功則對(duì)轉(zhuǎn)換后的值做大于驗(yàn)證。如果轉(zhuǎn)換失敗,則不做驗(yàn)證。
?
回調(diào)
如果驗(yàn)證失敗,可以使用回調(diào)做一些操作。
?
RuleFor(x => x.Weight).NotEmpty().OnFailure(x =>{Console.WriteLine("驗(yàn)證失敗");});預(yù)驗(yàn)證
如果需要每次調(diào)用驗(yàn)證器前運(yùn)行特定代碼,可以通過(guò)重寫(xiě)PreValidate方法來(lái)做到這一點(diǎn)。
public class StudentValidator : AbstractValidator<Student> {public StudentValidator(){RuleFor(x => x.Weight).NotEmpty();}protected override bool PreValidate(ValidationContext<Student> context,ValidationResult result){if (context.InstanceToValidate == null) return true;result.Errors.Add(new ValidationFailure("", "實(shí)體不能為null"));return false;} }?
福祿ICH.架構(gòu)出品
作者:福爾斯
2021年3月
總結(jié)
以上是生活随笔為你收集整理的客官,.NETCore无代码侵入的模型验证了解下的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 使用 .NET CLI 构建项目脚手架
- 下一篇: Win10 Terminal + WSL