前情提要
筆者最近在公司負責開發一個全新的API對外系統,因沒有舊系統包袱,可以設計統一Response,統一的ExceptionHandlerMiddleware,只多不嫌少的各種CustomizeException等等,然而接受客戶端的Request時,勢必要進行Validation作業,可以重捨FluentValidation的懷抱了,此篇就以筆者遇到的情境及解法介紹為主,讓我們看下去吧。
先列一下筆者這邊使用的套件的版本號
Fluent Validation11.7.1Fluent.Validation.AspNetCore11.3.0
再來敘述一下筆者這邊的環境遇到的問題,因EncryptKey放在資料庫的因素,需要Validator中連線資料庫並取得EncryptKey,這樣才能提早驗證客戶端傳來的加密字串是否可以成功解密,就不用等到商業邏輯處理時才爆錯,DB Access相關程式碼會使用到Async方法,參考https://docs.fluentvalidation.net/en/latest/async.html此篇中的說明,不適用ASP.Net Core的Validaion Pipeline,等於沒有使用到AutoValidation的好處,自己需要爾外透過IVlidator<T>.Validate這種手動驗證的方式進行驗證,也因此無法透過設定ConfigureApiBehaviorOptions來指定InvalidModelStateResponseFactory的統一Response,稍嫌可惜,筆者說的都在這篇文章上https://medium.com/codex/custom-error-responses-with-asp-net-core-6-web-api-and-fluentvalidation-888a3b16c80f。
驗證思路
筆者想像中,在每一個Action開頭中多了一個Validate的作業,只是型別不一樣,但行為是一致的
1 | public class OrderController: ControllerBase |
寫到這邊筆者想起剛開始撰寫MVC5的時候,老是想用上ActionFilter,盡量讓統一行為在ActionFilter中進行,達到DRY的精神。但不外乎常用到的情境就是ModelState的驗證,不過時回應統一的Response,這次情境只是多了一個型別的宣告,那就來用各Generic Action Filter吧,接著就誕生ValidationFilterAttribute
實作IAsyncActionFilter
因為非同步的行為,筆者這邊採用IAsyncActionFilter
1 | public class ValidationFilterAttribute<T> : IAsyncActionFilter |
註冊IAsyncActionFilter
註冊方式是滿簡單的,筆者這邊就透過dotnet core預設的DI,註冊為Scope類型服務
1 | services.AddScoped(typeof(ValidationFilterAttribute<>)); |
套用IAsyncActionFilter
撰寫完畢,註冊完成後,將套用在想套用的Action上吧
1 | public class OrderController : ControllerBase |
設定CascadeMode
依照Validate的角度來說,有前後相依性的,以筆者這邊的例子來說,加密字串為空的,根本不需要驗證解密是否可以成功,且解密驗證關係到連線資料庫,若能夠於加密字串為空這個Validation Rule驗證不過時,就不用往下驗證,對於執行效能來說是好的,FluentValidation也有提供其方便設定的方式,依照版本參考對應的設定,筆者這邊的版本11來說,分為
- Global Level
- Class Level
- Rule Level
1 | public class OrderRequestValidator : AbstractValidator<OrderRequestDto> |
結論
因目前開發的是一個全新的API站台對外系統,比較沒有舊系統的包袱,可以照比較正統的方式設計其系統,各司其職的概念,尚未包含商業邏輯簡單驗證還是交給Fluent Validation,擋在前面,到Service層就專心處理商業邏輯就好,職責明確之外,也少了很多code塞在同一個地方窘境,偵錯起來也不會亂,一舉數得,還不行動嗎?
參考
- https://docs.fluentvalidation.net/en/latest/index.html
- https://learn.microsoft.com/zh-tw/aspnet/core/mvc/controllers/filters?view=aspnetcore-7.0#dependency-injection
- [DotnetCore]後端驗證神器:Fluent Validation