網站首頁 編程語言 正文
需求
在響應請求處理的過程中,我們經常需要對請求參數的合法性進行校驗,如果參數不合法,將不繼續進行業務邏輯的處理。我們當然可以將每個接口的參數校驗邏輯寫到對應的Handle方法中,但是更好的做法是借助MediatR提供的特性,將這部分與實際業務邏輯無關的代碼整理到單獨的地方進行管理。
為了實現這個需求,我們需要結合FluentValidation和MediatR提供的特性。
目標
將請求的參數校驗邏輯從CQRS的Handler中分離到MediatR的Pipeline框架中處理。
原理與思路
MediatR不僅提供了用于實現CQRS的框架,還提供了IPipelineBehavior<TRequest, TResult>接口用于實現CQRS響應之前進行一系列的與實際業務邏輯不緊密相關的特性,諸如請求日志、參數校驗、異常處理、授權、性能監控等等功能。
在本文中我們將結合FluentValidation和IPipelineBehavior<TRequest, TResult>實現對請求參數的校驗功能。
實現
添加MediatR參數校驗Pipeline Behavior框架支持#
首先向Application項目中引入FluentValidation.DependencyInjectionExtensionsNuget包。為了抽象所有的校驗異常,先創建ValidationException類:
ValidationException.cs
namespace TodoList.Application.Common.Exceptions; public class ValidationException : Exception { public ValidationException() : base("One or more validation failures have occurred.") { } public ValidationException(string failures) : base(failures) { } }
參數校驗的基礎框架我們創建到Application/Common/Behaviors/中:
ValidationBehaviour.cs
using FluentValidation; using FluentValidation.Results; using MediatR; using ValidationException = TodoList.Application.Common.Exceptions.ValidationException; namespace TodoList.Application.Common.Behaviors; public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : notnull { private readonly IEnumerable<IValidator<TRequest>> _validators; // 注入所有自定義的Validators public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators) => _validators = validators; public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next) { if (_validators.Any()) { var context = new ValidationContext<TRequest>(request); var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken))); var failures = validationResults .Where(r => r.Errors.Any()) .SelectMany(r => r.Errors) .ToList(); // 如果有validator校驗失敗,拋出異常,這里的異常是我們自定義的包裝類型 if (failures.Any()) throw new ValidationException(GetValidationErrorMessage(failures)); } return await next(); } // 格式化校驗失敗消息 private string GetValidationErrorMessage(IEnumerable<ValidationFailure> failures) { var failureDict = failures .GroupBy(e => e.PropertyName, e => e.ErrorMessage) .ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray()); return string.Join(";", failureDict.Select(kv => kv.Key + ": " + string.Join(' ', kv.Value.ToArray()))); } }
在DependencyInjection中進行依賴注入:
DependencyInjection.cs
// 省略其他...
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>)
添加Validation Pipeline Behavior
接下來我們以添加TodoItem接口為例,在Application/TodoItems/CreateTodoItem/中創建CreateTodoItemCommandValidator:
CreateTodoItemCommandValidator.cs
using FluentValidation; using Microsoft.EntityFrameworkCore; using TodoList.Application.Common.Interfaces; using TodoList.Domain.Entities; namespace TodoList.Application.TodoItems.Commands.CreateTodoItem; public class CreateTodoItemCommandValidator : AbstractValidator<CreateTodoItemCommand> { private readonly IRepository<TodoItem> _repository; public CreateTodoItemCommandValidator(IRepository<TodoItem> repository) { _repository = repository; // 我們把最大長度限制到10,以便更好地驗證這個校驗 // 更多的用法請參考FluentValidation官方文檔 RuleFor(v => v.Title) .MaximumLength(10).WithMessage("TodoItem title must not exceed 10 characters.").WithSeverity(Severity.Warning) .NotEmpty().WithMessage("Title is required.").WithSeverity(Severity.Error) .MustAsync(BeUniqueTitle).WithMessage("The specified title already exists.").WithSeverity(Severity.Warning); } public async Task<bool> BeUniqueTitle(string title, CancellationToken cancellationToken) { return await _repository.GetAsQueryable().AllAsync(l => l.Title != title, cancellationToken); } }
其他接口的參數校驗添加方法與此類似,不再繼續演示。
驗證
啟動Api項目,我們用一個校驗會失敗的請求去創建TodoItem:
請求
響應
因為之前測試的時候已經在沒有加校驗的時候用同樣的請求生成了一個TodoItem,所以校驗失敗的消息里有兩項校驗都沒有滿足。
一點擴展
我們在前文中說了使用MediatR的PipelineBehavior可以實現在CQRS請求前執行一些邏輯,其中就包含了日志記錄,這里就把實現方式也放在下面,在這里我們使用的是Pipeline里的IRequestPreProcessor<TRequest>接口實現,因為只關心請求處理前的信息,如果關心請求處理返回后的信息,那么和前文一樣,需要實現IPipelineBehavior<TRequest, TResponse>接口并在Handle中返回response對象:
// 省略其他... var response = await next(); //Response _logger.LogInformation($"Handled {typeof(TResponse).Name}"); return response;
創建一個LoggingBehavior:
using System.Reflection; using MediatR.Pipeline; using Microsoft.Extensions.Logging; public class LoggingBehaviour<TRequest> : IRequestPreProcessor<TRequest> where TRequest : notnull { private readonly ILogger<LoggingBehaviour<TRequest>> _logger; // 在構造函數中后面我們還可以注入類似ICurrentUser和IIdentity相關的對象進行日志輸出 public LoggingBehaviour(ILogger<LoggingBehaviour<TRequest>> logger) { _logger = logger; } public async Task Process(TRequest request, CancellationToken cancellationToken) { // 你可以在這里log關于請求的任何信息 _logger.LogInformation($"Handling {typeof(TRequest).Name}"); IList<PropertyInfo> props = new List<PropertyInfo>(request.GetType().GetProperties()); foreach (var prop in props) { var propValue = prop.GetValue(request, null); _logger.LogInformation("{Property} : {@Value}", prop.Name, propValue); } } }
如果是實現IPipelineBehavior<TRequest, TResponse>接口,最后注入即可。
// 省略其他... services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehaviour<,>));
如果實現IRequestPreProcessor<TRequest>接口,則不需要再進行注入。
效果如下圖所示:
可以看到日志中已經輸出了Command名稱和請求參數字段值。
總結
在本文中我們通過FluentValidation和MediatR實現了不侵入業務代碼的請求參數校驗邏輯,在下一篇文章中我們將介紹.NET開發中會經常用到的ActionFilters。
參考資料
原文鏈接:https://www.cnblogs.com/code4nothing/p/15743335.html
相關推薦
- 2022-05-25 Properties與ResourceBundle的基本使用以及區別
- 2022-03-08 C++中的對象初始化操作代碼_C 語言
- 2022-04-12 qt5之QFile讀寫文件功能詳解_C 語言
- 2022-07-20 react中事件處理與柯里化的實現_React
- 2022-07-18 python中數組array和列表list的基本用法及區別解析_python
- 2024-03-04 layui樹形組件獲取復選框選中的id,禁用選中父節點后自動選中子節點功能
- 2022-07-03 Qt讀寫ini文件之QSettings用法_C 語言
- 2022-07-13 SpringCloud之http客戶端Feign
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支