如何将FluentValidation库与MediatR IPipelineBehavior一起使用?

时间:2018-09-26 12:00:39

标签: c# asp.net-core cqrs fluentvalidation mediatr

我开始探索MediatR,但是在从邮递员处调用GET端点时遇到了问题,API返回500 Internal Server Error。而且,我认为FluentValidation [ERROR] SonarQube server [http://localhost:9000] can not be reached 的实现引起了这个问题。也许我的实现不正确,或者我可能误会了。这是供他人查看的代码!

来自控制器的代码。

pipeline

处理程序代码。

using BusinessLayer.Test.Commands;
using BusinessLayer.Test.Queries;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
using System.Threading.Tasks;

namespace MediatRTestApi.Controllers
{
    [Route("api/v1/[controller]")]
    public class TestsController : Controller
    {
        readonly IMediator _mediator;

        public TestsController(IMediator mediator)
        {
            _mediator = mediator;
        }

        [HttpGet]
        public async Task<IActionResult> FindList([FromQuery]TestQueryRequest model)
        {
            var results = await _mediator.Send(model);

            return new OkObjectResult(new { results });
        }

        [HttpPost]
        public async Task<IActionResult> Post([FromBody]TestAddRequest model)
        {
            var response = await _mediator.Send(model).ConfigureAwait(false);

            if (response.Errors.Any())
            {
                return BadRequest(response.Errors);
            }

            return Ok(response.Result);
        }
    }
}

请求和响应代码。

using BusinessLayer.Pipelines;
using DataStores;
using MediatR;
using Microsoft.Extensions.Configuration;
using System.Threading;
using System.Threading.Tasks;

namespace BusinessLayer.Test.Commands
{
    public class TestAddHandler : IRequestHandler<TestAddRequest, Response>
    {
        readonly string _dbConnectionString;

        public TestAddHandler(IConfiguration configuration)
        {
            _dbConnectionString = configuration.GetSection("connectionStrings:oltpConnectionString").Value;
        }

        public async Task<Response> Handle(TestAddRequest request, CancellationToken cancellationToken)
        {
            var poco = ParseRequest(request);
            await AddTestRecord(poco);

            return new Response("Record is added.");
        }

        async Task AddTestRecord(TestPoco poco)
        {
            var store = await new TestStore(_dbConnectionString).InsertAsync(poco);
        }

        TestPoco ParseRequest(TestAddRequest request)
        {
            return new TestPoco() { TestName = request.TestName, IsActive = true };
        }
    }
}

FluentValidation管道的代码。

using BusinessLayer.Pipelines;
using FluentValidation;
using MediatR;

namespace BusinessLayer.Test.Commands
{
    public class TestAddRequest : IRequest<Response>
    {
        public string TestName { get; set; }
    }

    public class TestAddRequestValidator : AbstractValidator<TestAddRequest>
    {
        readonly IMediator _mediator;

        public TestAddRequestValidator(IMediator mediator)
        {
            _mediator = mediator;

            RuleFor(x => x.TestName).NotEmpty();
        }
    }
}

using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace BusinessLayer.Pipelines
{
    public class Response
    {
        readonly IList<string> _messages = new List<string>();

        public IEnumerable<string> Errors { get; }
        public object Result { get; }

        public Response() => Errors = new ReadOnlyCollection<string>(_messages);

        public Response(object result) : this()
        {
            Result = result;
        }

        public Response AddError(string message)
        {
            _messages.Add(message);
            return this;
        }
    }
}

像这样在using FluentValidation; using FluentValidation.Results; using MediatR; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace BusinessLayer.Pipelines { public class FailFastRequestBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse> where TResponse : Response { private readonly IEnumerable<IValidator> _validators; public FailFastRequestBehavior(IEnumerable<IValidator<TRequest>> validators) { _validators = validators; } public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next) { var failures = _validators .Select(v => v.Validate(request)) .SelectMany(result => result.Errors) .Where(f => f != null) .ToList(); return failures.Any() ? Errors(failures) : next(); } private static Task<TResponse> Errors(IEnumerable<ValidationFailure> failures) { var response = new Response(); foreach (var failure in failures) { response.AddError(failure.ErrorMessage); } return Task.FromResult(response as TResponse); } } } 中注册管道:

Startup.cs

执行命令时,出现以下错误。

services.AddTransient(typeof(IPipelineBehavior<,>), typeof(FailFastRequestBehavior<,>));

请注意,当我在 fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1] An unhandled exception has occurred while executing the request. System.NullReferenceException: Object reference not set to an instance of an object. at MediatR.Internal.RequestHandlerWrapperImpl`2.<>c__DisplayClass0_1.<Handle>b__2() at MediatR.Internal.RequestHandlerWrapperImpl`2.Handle(IRequest`1 request, CancellationToken cancellationToken, ServiceFactory serviceFactory) at MediatR.Mediator.Send[TResponse](IRequest`1 request, CancellationToken cancellationToken) at MediatRTestApi.Controllers.TestsController.FindList(TestQueryRequest model) in C:\Test\MediatRTestApi\MediatRTestApi\Controllers\TestsController.cs:line 23 at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at System.Threading.Tasks.ValueTask`1.get_Result() at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync() at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync() at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context) at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync() at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync() at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ExceptionContext context) at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter() at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context) at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync() at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync() at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context) info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2] Request finished in 507.141ms 500 text/html; charset=utf-8 中分离FluentValidation管道时,我没有遇到问题,但是当然,startup.cs方法没有进行验证。

1 个答案:

答案 0 :(得分:1)

public class RequestValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
    private readonly IEnumerable<IValidator<TRequest>> _validators;

    public RequestValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
    {
        _validators = validators;
    }

    public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
    {
        var context = new ValidationContext(request);

        var failures = _validators
            .Select(v => v.Validate(context))
            .SelectMany(result => result.Errors)
            .Where(f => f != null)
            .ToList();

        if (failures.Count != 0)
        {
            throw new Core.Exceptions.ValidationException(failures);
        }

        return next();
    }
}


// Ioc container config.
      services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestValidationBehavior<,>));