使用.NET 6开发TodoList应用(14)——实现查询过滤


系列导航及源代码

需求

在查询请求中,还有一类常见的场景是过滤查询,也就是有限制条件的查询,落在数据库层面就是常用的Where查询子句。实现起来也很简单。

目标

实现查询过滤的功能

原理与思路

查询过滤的请求有两种方式,一种是采用POST方法,将查询条件放在请求体中,但是这种方式实际上和Restful的动词语义产生了矛盾,从Restful API成熟度模型的角度来说,还停留在Level 1阶段(仅知道地址,不符合动词语义),详情参考理查森成熟度模型;还有一种方法是采用GET方法,将查询条件放在查询字符串里,我们将采用第二种方式来实现。

实现

我们还是通过查询TodoItem列表来演示查询过滤,和前面的文章一样,我们先来实现一个查询的Query请求对象:

  • GetTodoItemsWithConditionQuery.cs
using AutoMapper;
using AutoMapper.QueryableExtensions;
using MediatR;
using TodoList.Application.Common.Interfaces;
using TodoList.Application.Common.Mappings;
using TodoList.Application.Common.Models;
using TodoList.Domain.Entities;
using TodoList.Domain.Enums;

namespace TodoList.Application.TodoItems.Queries.GetTodoItems;

public class GetTodoItemsWithConditionQuery : IRequest<PaginatedList<TodoItemDto>>
{
    public Guid ListId { get; set; }
    public bool? Done { get; set; }
    public PriorityLevel? PriorityLevel { get; set; }
    public int PageNumber { get; set; } = 1;
    public int PageSize { get; set; } = 10;
}

public class GetTodoItemsWithConditionQueryHandler : IRequestHandler<GetTodoItemsWithConditionQuery, PaginatedList<TodoItemDto>>
{
    private readonly IRepository<TodoItem> _repository;
    private readonly IMapper _mapper;

    public GetTodoItemsWithConditionQueryHandler(IRepository<TodoItem> repository, IMapper mapper)
    {
        _repository = repository;
        _mapper = mapper;
    }

    public async Task<PaginatedList<TodoItemDto>> Handle(GetTodoItemsWithConditionQuery request, CancellationToken cancellationToken)
    {
        return await _repository
            .GetAsQueryable(x => x.ListId == request.ListId 
                                 && (!request.Done.HasValue || x.Done == request.Done) 
                                 && (!request.PriorityLevel.HasValue || x.Priority == request.PriorityLevel))
            .OrderBy(x => x.Title)
            .ProjectTo<TodoItemDto>(_mapper.ConfigurationProvider)
            .PaginatedListAsync(request.PageNumber, request.PageSize);
    }
}

甚至为了结合之前讲的请求校验,我们可以在这里增加一个校验规则:

  • GetTodoItemValidator.cs
using FluentValidation;

namespace TodoList.Application.TodoItems.Queries.GetTodoItems;

public class GetTodoItemValidator : AbstractValidator<GetTodoItemsWithConditionQuery>
{
    public GetTodoItemValidator()
    {
        RuleFor(x => x.ListId).NotEmpty().WithMessage("ListId is required.");
        RuleFor(x => x.PageNumber).GreaterThanOrEqualTo(1).WithMessage("PageNumber at least greater than or equal to 1.");
        RuleFor(x => x.PageSize).GreaterThanOrEqualTo(1).WithMessage("PageSize at least greater than or equal to 1.");
    }
}

接下来在TodoItemController中实现请求处理,我们直接修改上一篇讲分页的那个请求成如下(如果在上一篇的基础上新增Action的话,会导致路由的歧义):

  • TodoItemController.cs
// 省略其他...
[HttpGet]
public async Task<ApiResponse<PaginatedList<TodoItemDto>>> GetTodoItemsWithCondition([FromQuery] GetTodoItemsWithConditionQuery query)
{
    return ApiResponse<PaginatedList<TodoItemDto>>.Success(await _mediator.Send(query));
}

验证

启动Api项目,执行创建TodoList的请求:

请求仅携带Done过滤条件时

  • 请求
    image

  • 响应
    image

请求仅携带PriorityLevel过滤条件时

  • 请求
    image

  • 响应
    image

请求携带完整的过滤条件时

  • 请求
    image

  • 响应
    image

请求参数不合法时

  • 请求
    image
    我将pageNumber传成了0。

  • 响应
    image

总结

对于查询过滤这个需求来说,实现起来还是很简单的,但是这里其实隐藏了一个很重要的问题:如果查询的参数太多,比如存在多个Guid类型的字段过滤,URL的总长度是有可能超出浏览器或者服务器Host环境限制的,在这种情况下,我们是否还要坚持使用符合理查森成熟度模型Level 2的GET请求呢?

关于这个问题的争论一直以来就没有停过,首先说我个人的结论:可以采用POST的方式去变通,没必要为难自己(虽然这话肯定会引来Restful拥趸的嘲讽)。可是如果对成熟度有硬性的要求,我们如何实现?比较通用的解决方案是将一步GET查询拆成两步GET查询。但是更多的情况,是我认为如果出现了这种情况,就放弃Restful风格吧。

其实GraphQL才是王道。

参考资料

  1. 理查森成熟度模型
  2. GraphQL
文章来源:https://www.cnblogs.com/code4nothing/p/build-todolist-14.html

版权声明:本文为YES开发框架网发布内容,转载请附上原文出处连接
管理员
上一篇:C# Abp框架入门系列文章(一)
下一篇:记一次本地正常上线接口报404
评论列表

发表评论

评论内容
昵称:
关联文章

使用.NET 6开发TodoList应用(14)——实现查询过滤
使用.NET 6开发TodoList应用(16)——实现查询排序
使用.NET 6开发TodoList应用(15)——实现查询搜索
使用.NET 6开发TodoList应用(13)——实现查询分页
使用.NET 6开发TodoList应用(12)——实现ActionFilter
使用.NET 6开发TodoList应用(22)——实现缓存
使用.NET 6开发TodoList应用(9)——实现PUT请求
使用.NET 6开发TodoList应用(25)——实现RefreshToken
使用.NET 6开发TodoList应用(17)——实现数据塑形
使用.NET 6开发TodoList应用(7)——使用AutoMapper实现GET请求
使用.NET 6开发TodoList应用(31)——实现基于Github Actions和ACI的CI/CD
使用.NET 6开发TodoList应用(6)——使用MediatR实现POST请求
使用.NET 6开发TodoList应用(29)——实现静态字符串本地化功能
使用.NET 6开发TodoList应用(24)——实现基于JWT的Identity功能
使用.NET 6开发TodoList应用(21)——实现API版本控制
使用.NET 6开发TodoList应用(28)——实现应用程序健康检查
使用.NET 6开发TodoList应用(30)——实现Docker打包和部署
使用.NET 6开发TodoList应用(11)——使用FluentValidation和MediatR实现接口请求验证
使用.NET 6开发TodoList应用(23)——实现请求限流
使用.NET 6开发TodoList应用(8)——实现全局异常处理

联系我们
联系电话:15090125178(微信同号)
电子邮箱:garson_zhang@163.com
站长微信二维码
微信二维码