.NET 6开发TodoList应用之实现接口请求验证
需求
在响应请求处理的过程中,我们经常需要对请求参数的合法性进行校验,如果参数不合法,将不继续进行业务逻辑的处理。我们当然可以将每个接口的参数校验逻辑写到对应的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。
参考资料
How to use MediatR Pipeline Behaviours
到此这篇关于.NET 6开发TodoList应用之实现接口请求验证的文章就介绍到这了,更多相关.NET 6接口请求验证内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
asp.net DbProviderFactory的使用-示例
NET 2.0有一个抽象工厂模式的典型应用:通过DBProviderFactory 可以对不同数据库进行操作。2009-11-11
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 动态加载用户自定义控件,并转换成HTML代码
Ajax现在已经是相当流行的技术了,Ajax不仅是想服务器端发送消息,更重要的是无刷新的重载页面。2010-03-03
MVC默认路由实现分页(PagerExtend.dll下载)
这篇文章主要介绍了MVC默认路由实现分页,采用bootstrap的样式,文末提供了PagerExtend.dll下载地址,感兴趣的小伙伴们可以参考一下2016-07-07


最新评论