.NET 6開發(fā)TodoList應用之實現接口請求驗證
需求
在響應請求處理的過程中,我們經常需要對請求參數的合法性進行校驗,如果參數不合法,將不繼續(xù)進行業(yè)務邏輯的處理。我們當然可以將每個接口的參數校驗邏輯寫到對應的Handle方法中,但是更好的做法是借助MediatR提供的特性,將這部分與實際業(yè)務邏輯無關的代碼整理到單獨的地方進行管理。
為了實現這個需求,我們需要結合FluentValidation和MediatR提供的特性。
目標
將請求的參數校驗邏輯從CQRS的Handler中分離到MediatR的Pipeline框架中處理。
原理與思路
MediatR不僅提供了用于實現CQRS的框架,還提供了IPipelineBehavior<TRequest, TResult>接口用于實現CQRS響應之前進行一系列的與實際業(yè)務邏輯不緊密相關的特性,諸如請求日志、參數校驗、異常處理、授權、性能監(jiān)控等等功能。
在本文中我們將結合FluentValidation和IPipelineBehavior<TRequest, TResult>實現對請求參數的校驗功能。
實現
添加MediatR參數校驗Pipeline Behavior框架支持#
首先向Application項目中引入FluentValidation.DependencyInjectionExtensionsNuget包。為了抽象所有的校驗異常,先創(chuàng)建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)
{
}
}
參數校驗的基礎框架我們創(chuàng)建到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/中創(chuàng)建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);
}
}
其他接口的參數校驗添加方法與此類似,不再繼續(xù)演示。
驗證
啟動Api項目,我們用一個校驗會失敗的請求去創(chuàng)建TodoItem:
請求

響應

因為之前測試的時候已經在沒有加校驗的時候用同樣的請求生成了一個TodoItem,所以校驗失敗的消息里有兩項校驗都沒有滿足。
一點擴展
我們在前文中說了使用MediatR的PipelineBehavior可以實現在CQRS請求前執(zhí)行一些邏輯,其中就包含了日志記錄,這里就把實現方式也放在下面,在這里我們使用的是Pipeline里的IRequestPreProcessor<TRequest>接口實現,因為只關心請求處理前的信息,如果關心請求處理返回后的信息,那么和前文一樣,需要實現IPipelineBehavior<TRequest, TResponse>接口并在Handle中返回response對象:
// 省略其他...
var response = await next();
//Response
_logger.LogInformation($"Handled {typeof(TResponse).Name}");
return response;
創(chuàng)建一個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實現了不侵入業(yè)務代碼的請求參數校驗邏輯,在下一篇文章中我們將介紹.NET開發(fā)中會經常用到的ActionFilters。
參考資料
How to use MediatR Pipeline Behaviours?
到此這篇關于.NET 6開發(fā)TodoList應用之實現接口請求驗證的文章就介紹到這了,更多相關.NET 6接口請求驗證內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
asp.net DbProviderFactory的使用-示例
NET 2.0有一個抽象工廠模式的典型應用:通過DBProviderFactory 可以對不同數據庫進行操作。2009-11-11
微信公眾平臺開發(fā)之認證"成為開發(fā)者".Net代碼解析
這篇文章主要為大家詳細解析了微信公眾平臺開發(fā)之認證"成為開發(fā)者".Net代碼,感興趣的小伙伴們可以參考一下2016-06-06
ASP.NET The system cannot find the file specified解決辦法
這篇文章主要介紹了ASP.NET The system cannot find the file specified解決辦法的相關資料,需要的朋友可以參考下2016-11-11
ASP.NET生成eurl.axd Http異常錯誤的處理方法
在IIS6中同時啟用了ASP.NET 2.0 和 ASP.NET 4.0 后,網站程序可能會出現如下錯誤:“ System.Web.HttpException: Path ‘//eurl.axd/‘ was not found. ”2011-05-05
解析.netcore項目中IStartupFilter使用教程
netcore項目中有些服務是在通過中間件來通信的,比如orleans組件,今天通過實例代碼給大家介紹下netcore項目中IStartupFilter使用教程,感興趣的朋友一起看看吧2021-11-11
asp.net LC.exe已退出代碼為 -1的原因分析及解決方法
錯誤“LC.exe”已退出,代碼為 -1。是VS2005,并且在項目中引用了第三方組件。2013-06-06
Asp.net 動態(tài)加載用戶自定義控件,并轉換成HTML代碼
Ajax現在已經是相當流行的技術了,Ajax不僅是想服務器端發(fā)送消息,更重要的是無刷新的重載頁面。2010-03-03
MVC默認路由實現分頁(PagerExtend.dll下載)
這篇文章主要介紹了MVC默認路由實現分頁,采用bootstrap的樣式,文末提供了PagerExtend.dll下載地址,感興趣的小伙伴們可以參考一下2016-07-07

