我开始探索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
方法不会进行验证。