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