3

我开始探索MediatR,但是当从 Postman 调用 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方法不会进行验证。

4

1 回答 1

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<,>));
于 2020-02-12T18:40:05.783 回答