- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
本文将重点介绍如何通过MediatR的管道功能将FluentValidation集成到项目中实现验证功能.
CQRS(Command Query Responsibility Segregation)也叫命令查询职责分离,是近年来非常流行的应用程序架构模式。CQRS 背后的理念是在逻辑上将应用程序的流程分成两个独立的流程,即命令或查询.
命令用于改变应用程序的状态。对应CRUD的创建、更新和删除部分。查询用于检索应用程序中的信息,对应CRUD的读取部分.
优点:
缺点:
MediatR 使用接口(interface)来表示命令和查询。在我们的项目中,我们将为命令和查询创建单独的抽象.
首先,让我们看看接口是如何定义的:
using MediatR;
namespace Application.Abstractions.Messaging
{
public interface ICommand<out TResponse> : IRequest<TResponse>
{
}
}
using MediatR;
namespace Application.Abstractions.Messaging
{
public interface IQuery<out TResponse> : IRequest<TResponse>
{
}
}
我们在声明 TResponse 泛型时使用了 out 关键字,这表示它是协变的。这样,我们就可以使用比泛型参数指定的类型更多的派生类型。要了解有关协变和逆变的更多信息,请查看微软文档.
此外,为了完整起见,我们需要对命令和查询处理程序进行单独的抽象.
using MediatR;
namespace Application.Abstractions.Messaging
{
public interface ICommandHandler<in TCommand, TResponse> : IRequestHandler<TCommand, TResponse>
where TCommand : ICommand<TResponse>
{
}
}
using MediatR;
namespace Application.Abstractions.Messaging
{
public interface IQueryHandler<in TQuery, TResponse> : IRequestHandler<TQuery, TResponse>
where TQuery : IQuery<TResponse>
{
}
}
这里留下一个小问题,MediatR已经提供了 IRequest 和 IRequest<TResponse> 两个接口,那我们为什么还要再次定义 IQuery<out TResponse> 和 ICommand<out TResponse> 呢?
FluentValidation 库允许我们轻松地为我们的类定义非常丰富的自定义验证。由于我们正在实现 CQRS,所以这里我们仅讨论对Command进行验证。由于Query对象仅仅是从应用程序获取数据,意思我们不必多此一举为Query设计验证器.
我们先设计一个 UpdateUserCommand 。
public sealed record UpdateUserCommand(int UserId, string FirstName, string LastName) : ICommand<Unit>;
Unit 是MediatR定义的一个特殊类,表示请求不返回数据,相当于 void 或 Task .
这个命令将用于更新已有用户(通过UserId查找)的FirstName和LastName,关于MediatR如何新增、查询和修改数据,在之前的文章中我们已经介绍过了,这里不再赘述.
接下来我们需要为 UpdateUserCommand 定义一个验证器:
public sealed class UpdateUserCommandValidator : AbstractValidator<UpdateUserCommand>
{
public UpdateUserCommandValidator()
{
RuleFor(x => x.UserId).NotEmpty();
RuleFor(x => x.FirstName).NotEmpty().MaximumLength(100);
RuleFor(x => x.LastName).NotEmpty().MaximumLength(100);
}
}
此验证器将对 UpdateUserCommand 的属性进行以下验证:
CQRS 模式使用命令和查询来传达信息并接收响应。实质上是请求-响应管道。这使我们能够轻松地围绕通过管道的每个请求引入其他行为,而无需实际修改原始请求.
您可能熟悉这种名为装饰器模式的技术。使用装饰器模式的典型例子就是ASP.NET Core中间件。MediatR与中间件的概念类似,称为: IPipelineBehavior 。
public interface IPipelineBehavior<in TRequest, TResponse> where TRequest : notnull
{
Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next);
}
PipelineBehavior是请求实例的包装器,在如何实现它方面为您提供了很大的灵活性。PipelineBehavior非常适合应用程序中的横切关注点。横切关注点的很好的例子是日志记录、缓存,当然还有验证! 。
为了在 CQRS 管道中实现验证,我们将使用刚才谈到的概念,即 MediatR 的 IPipelineBehavior 和 FluentValidation.
首先我们创建一个 ValidationBehavior 。
public sealed class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : class, ICommand<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators) => _validators = validators;
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if (!_validators.Any())
{
return await next();
}
var context = new ValidationContext<TRequest>(request);
var errorsDictionary = _validators
.Select(x => x.Validate(context))
.SelectMany(x => x.Errors)
.Where(x => x != null)
.GroupBy(
x => x.PropertyName,
x => x.ErrorMessage,
(propertyName, errorMessages) => new
{
Key = propertyName,
Values = errorMessages.Distinct().ToArray()
})
.ToDictionary(x => x.Key, x => x.Values);
if (errorsDictionary.Any())
{
throw new ValidationException(errorsDictionary);
}
return await next();
}
}
为了处理遇到验证错误时抛出的 ValidationException ,我们可以使用 ASP.NET Core的 IMiddleware 接口.
internal sealed class ExceptionHandlingMiddleware : IMiddleware
{
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
public ExceptionHandlingMiddleware(ILogger<ExceptionHandlingMiddleware> logger) => _logger = logger;
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
try
{
await next(context);
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
await HandleExceptionAsync(context, e);
}
}
private static async Task HandleExceptionAsync(HttpContext httpContext, Exception exception)
{
var statusCode = GetStatusCode(exception);
var response = new
{
title = GetTitle(exception),
status = statusCode,
detail = exception.Message,
errors = GetErrors(exception)
};
httpContext.Response.ContentType = "application/json";
httpContext.Response.StatusCode = statusCode;
await httpContext.Response.WriteAsync(JsonSerializer.Serialize(response));
}
private static int GetStatusCode(Exception exception) =>
exception switch
{
BadRequestException => StatusCodes.Status400BadRequest,
NotFoundException => StatusCodes.Status404NotFound,
ValidationException => StatusCodes.Status422UnprocessableEnttity,
_ => StatusCodes.Status500InternalServerError
};
private static string GetTitle(Exception exception) =>
exception switch
{
ApplicationException applicationException => applicationException.Title,
_ => "Server Error"
};
private static IReadOnlyDictionary<string, string[]> GetErrors(Exception exception)
{
IReadOnlyDictionary<string, string[]> errors = null;
if (exception is ValidationException validationException)
{
errors = validationException.ErrorsDictionary;
}
return errors;
}
}
在运行应用程序之前,我们需要确保已向 DI 容器注册了所有服务。MediatR的DI注入方式之前已经介绍过,这里主要演示FluentValidation的注入。由于 ValidationBehavior 依赖 IValidator<T> ,因此需要注入我们定义的Validator.
// 在Startup.cs中配置
services.AddValidatorsFromAssembly(typeof(Application.AssemblyReference).Assembly);
// 在Program.cs中配置(≥ net 6.0)
builder.Services.AddValidatorsFromAssembly(typeof(Application.AssemblyReference).Assembly);
最后我们需要将 ExceptionHandlingMiddleware 也注册到DI容器和ASP.NET Core的管道中:
// 在Startup.cs中配置
services.AddTransient<ExceptionHandlingMiddleware>();
// 在Program.cs中配置(≥ net 6.0)
builder.Services.AddTransient<ExceptionHandlingMiddleware>();
app.UseMiddleware<ExceptionHandlingMiddleware>();
在项目的Controllers文件夹中找到UserController:
/// <summary>
/// The users controller.
/// </summary>
[ApiController]
[Route("api/[controller]")]
public sealed class UsersController : ControllerBase
{
private readonly ISender _sender;
/// <summary>
/// Initializes a new instance of the <see cref="UsersController"/> class.
/// </summary>
/// <param name="sender"></param>
public UsersController(ISender sender) => _sender = sender;
/// <summary>
/// Updates the user with the specified identifier based on the specified request, if it exists.
/// </summary>
/// <param name="userId">The user identifier.</param>
/// <param name="request">The update user request.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>No content.</returns>
[HttpPut("{userId:int}")]
public async Task<IActionResult> UpdateUser(int userId, [FromBody] UpdateUserRequest request, CancellationToken cancellationToken)
{
var command = request.Adapt<UpdateUserCommand>() with
{
UserId = userId
};
await _sender.Send(command, cancellationToken);
return NoContent();
}
}
我们可以看到,UpdateUser 操作非常简单,它从路由中获取用户Id,从请求正文中获取FirstName和LastName,然后创建一个新的 UpdateUserCommand实例并且通过管道发送命令。最后返回204(请求成功但无响应内容)状态码.
接下来我们通过Swagger调用API接口:
可以看到,请求的FirstName和LastName都是空白字符串.
补充内容之后再次发送请求.
在本文中,我们介绍了CQRS 模式的一些更高级的概念,以及如何在应用程序中通过横切的方式实现数据验证,同时也简单的介绍了如何通过ASP.NTE Core的中间件实现全局异常处理.
点关注,不迷路.
如果您喜欢这篇文章,请不要忘记 点赞、关注、转发 ,谢谢!如果您有任何高见,欢迎在评论区留言讨论…… 。
最后此篇关于使用MediatR和FluentValidation实现CQRS应用程序的数据验证的文章就讲到这里了,如果你想了解更多关于使用MediatR和FluentValidation实现CQRS应用程序的数据验证的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
在 JSF2 应用程序中遇到验证属性的问题时,有两种主要方法。 使用 Annotation 在 ManagedBean 上定义验证 @ManagedBean public class MyBean {
我想实现一个不常见的功能,我认为 jquery 验证插件将是最好的方法(如果您在没有插件的情况下建议和回答,我们也会欢迎)。我想在用户在输入字段中输入正确的单词后立即隐藏表单。我试过这个: $("
我有几个下拉菜单(类名为month_dropdown),并且下拉菜单的数量不是恒定的。我怎样才能为它们实现 NotEqual 验证。我正在使用 jQuery 验证插件。 这就是我写的 - jQuery
我设法制作了这个网址验证代码并且它起作用了。但我面临着一个问题。我认为 stackoverflow 是获得解决方案的最佳场所。 function url_followers(){ var url=do
我目前正在使用后端服务,该服务允许用户在客户端应用程序上使用 Google Games 库登录。 用户可以通过他们的 gplay ID 向我们发送信息,以便登录或恢复旧帐户。用户向我们发送以下内容,包
我正在尝试验证输入以查看它是否是有效的 IP 地址(可能是部分地址)。 可接受的输入:172、172.112、172.112.113、172.112.113.114 Not Acceptable 输入
我从 Mongoose 验证中得到这条消息: 'Validator failed for path phone with value ``' 这不应该发生,因为不需要电话。 这是我的模型架构: var
我一直在尝试使用Python-LDAP (版本 2.4.19)在 MacOS X 10.9.5 和 Python 2.7.9 下 我想在调用 .start_tls_s() 后验证与给定 LDAP 服务
我正在处理一个仅与 IE6 兼容的旧 javascript 项目(抱歉...),我想仅在 VS 2017 中禁用此项目的 ESLint/CSLint/Javascript 验证/CSS 验证。 我知道
我正在寻找一种方法来验证 Spring 命令 bean 中的 java.lang.Double 字段的最大值和最小值(一个值必须位于给定的值范围之间),例如, public final class W
我正在尝试在 springfuse(JavaEE 6 + Spring Framework (针对 Jetty、Tomcat、JBoss 等)) 和 maven 的帮助下构建我的 webapps 工作
我试图在我们的项目中使用 scalaz 验证,但遇到了以下情况: def rate(username: String, params: Map[String, String]): Validation
我有一个像这样的 Yaml 文件 name: hhh_aaa_bbb arguments: - !argument name: inputsss des
我有一个表单,人们可以单击并向表单添加字段,并且我需要让它在单击时验证这些字段中的值。 假设我单击它两次并获取 2 个独立的字段集,我需要旋转 % 以确保它在保存时等于 100。 我已放入此函数以使其
在我的页面中有一个选项可以创建新的日期字段输入框。用户可以根据需要创建尽可能多的“截止日期”和“起始日期”框。就像, 日期_to1 || date_from1 日期到2 ||日期_from2 date
我有一个像这样的 Yaml 文件 name: hhh_aaa_bbb arguments: - !argument name: inputsss des
有没有办法在动态字段上使用 jquery 验证表单。 我想将其设置为必填字段 我正在使用 Jsp 动态创建表单字段。 喜欢 等等...... 我想使用必需的表单字段验证此表单字段。 最佳答
嗨,任何人都可以通过提供 JavaScript 代码来帮助我验证用户名文本框不应包含数字,它只能包含一个字符。 最佳答案 使用正则表达式: (\d)+ 如果找到匹配项,则字符串中就有一个数字。 关于J
我有两个输入字段holidayDate和Description(id=tags) $(document).ready(function() {
我遇到了这个问题,这些验证从电子邮件验证部分开始就停止工作。 我只是不明白为什么即使经过几天的观察,只是想知道是否有人可以在这里指出我的错误? Javascript部分: function valid
我是一名优秀的程序员,十分优秀!