Как использовать библиотеку FluentValidation с MediatR IPipelineBehavior? - PullRequest
0 голосов
/ 26 сентября 2018

Я начал исследовать MediatR , но у меня возникают проблемы, когда конечная точка GET вызывается из Почтальона, API возвращает 500 Internal Server Error.И я думаю, что реализация FluentValidation 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 };
        }
    }
}

Запрос иКод ответа.

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;
        }
    }
}

Код для FluentValidation Pipeline.

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, я не получаю проблему, но, конечно, метод Post не 'сделать его проверку.

...