MediatR CQRS - 如何处理不存在的资源(asp.net core web api)

rej*_*y11 9 c# mediatr asp.net-core

因此,我最近开始学习如何将 MediatR 库与 ASP.NET Core Web API 一起使用,但我不确定当对不存在的资源发出 DELETE/PUT/PATCH 请求时如何返回 NotFound()。

如果我们以 DELETE 为例,这是我的控制器操作:

[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
    await Mediator.Send(new DeleteCourseCommand {Id = id});

    return NoContent();
}
Run Code Online (Sandbox Code Playgroud)

命令:

public class DeleteCourseCommand : IRequest
{
    public int Id { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

命令处理程序:

public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand>
{
    private readonly UniversityDbContext _context;

    public DeleteCourseCommandHandler(UniversityDbContext context)
    {
        _context = context;
    }

    public async Task<Unit> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
    {
        var course = await _context.Courses.FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken);


        if (course != null)
        {
            _context.Courses.Remove(course);
            var saveResult = await _context.SaveChangesAsync(cancellationToken);
            if (saveResult <= 0)
            {
                throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
            }
        }

        return Unit.Value;
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您在 Handle 方法中看到的,如果保存时出现错误,则会抛出异常,导致 500 内部服务器错误(我相信这是正确的)。但是如果没有找到课程,我如何将其反馈给控制器上的操作?是否只是调用查询以获取控制器操作中的课程,然后在它不存在时返回 NotFound() 或调用命令删除课程的情况?这当然可以工作,但是在我经历过的所有示例中,我还没有遇到使用两个 Mediator 调用的 Action。

Kir*_*kin 6

MediatR 支持请求/响应模式,它允许您从处理程序类返回响应。要使用这种方法,您可以使用 的通用版本IRequest,如下所示:

public class DeleteCourseCommand : IRequest<bool>
    ...
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们声明这bool将是响应类型。bool为了简单起见,我在这里使用:我建议对您的最终实现使用更具描述性的内容,但bool足以解释目的。

接下来,您可以更新您的DeleteCourseCommandHandler以使用这种新的响应类型,如下所示:

public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand, bool>
{
    ...

    public async Task<bool> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
    {
        var course = ...

        if (course == null)
            return false; // Simple example, where false means it wasn't found.

        ...

        return true;
    }
}
Run Code Online (Sandbox Code Playgroud)

IRequestHandler目前正在实施的有两个泛型类型,命令和响应。这需要更新 的签名Handle以返回一个bool而不是Unit(在您的问题中,Unit未使用)。

最后,您需要更新您的Delete操作以使用新的响应类型,如下所示:

public async Task<IActionResult> Delete(int id)
{
    var courseWasFound = await Mediator.Send(new DeleteCourseCommand {Id = id});

    if (!courseWasFound)
        return NotFound();

    return NoContent();
}
Run Code Online (Sandbox Code Playgroud)

  • 请注意,HTTP DELETE 不应返回 NotFound。它应该是幂等的。无论资源在调用之前是否存在,它都应该返回 NoContent。 (2认同)
  • @YuliBonner 是幂等的意味着多次调用后系统的状态不会改变。这并不意味着您无法向调用者发出资源是否存在的信号。 (2认同)

Tod*_*ton 6

我喜欢从我的命令返回事件。该命令告诉您的应用程序客户端希望它做什么。响应是它实际所做的。

\n\n

BTW\xe2\x80\x94\'s 表示命令处理程序应该返回任何内容。这实际上仅在完全异步的环境中才是正确的,在该环境中,只有在响应客户端接受命令后的某个时间,命令才会完成。在这种情况下,您将返回Task<Unit>并发布这些事件。一旦它们被提出,客户将通过其他渠道获取它们,例如 SignalR 集线器。无论哪种方式,事件都是告诉客户应用程序中发生了什么的最佳方式。

\n\n

首先为您的事件定义一个接口

\n\n
public interface IEvent\n{\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后,为命令中可能发生的每件事创建事件。如果您想使用这些信息执行某些操作,则可以在其中包含信息,或者如果类本身就足够了,则将它们留空。

\n\n
public class CourseNotFoundEvent : IEvent\n{\n\n}\n\npublic class CourseDeletedEvent : IEvent\n{\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在,让您的命令返回一个事件接口。

\n\n
public class DeleteCourseCommand : IRequest<IEvent>\n{\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

你的处理程序看起来像这样:

\n\n
public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand, IEvent>\n{\n    private readonly UniversityDbContext _context;\n\n    public DeleteCourseCommandHandler(UniversityDbContext context)\n    {\n        _context = context;\n    }\n\n    public async Task<IEvent> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)\n    {\n        var course = await _context.Courses.FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken);\n\n        if (course is null) \n            return new CourseNotFoundEvent();\n\n        _context.Courses.Remove(course);\n        var saveResult = await _context.SaveChangesAsync(cancellationToken);\n        if (saveResult <= 0)\n        {\n            throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");\n        }\n\n        return new CourseDeletedEvent();\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

最后,您可以在 Web API 上使用模式匹配来根据返回的事件执行操作。

\n\n
[HttpDelete("{id}")]\npublic async Task<IActionResult> Delete(int id)\n{\n    var @event = await Mediator.Send(new DeleteCourseCommand {Id = id});\n\n    if(@event is CourseNotFoundEvent)\n        return NotFound();\n\n    return NoContent();\n}\n
Run Code Online (Sandbox Code Playgroud)\n