Use. NET 6 Development of TodoList Application (14) - Implement Query Filtering

Series Navigation and Source Code


A common scenario in query requests is to filter queries, that is, queries with limited conditions, Where query clauses are commonly used at the database level. It's also easy to implement.


Implement query filtering

Principles and ideas

There are two ways to filter requests by query. One is to use the POST method to place the query condition in the request body, but this method actually contradicts Restful's verb semantics. From the perspective of the Restful API maturity model, it still stays in Level 1 (only know the address, not the verb semantics). Detailed reference Richardson maturity model ; Another way is to use the GET method, place the query criteria in the query string, and we'll do the second.


We also demonstrate Query filtering by querying the TodoItem list. Like the previous article, we first implement a Query request object:

  • 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)
            .PaginatedListAsync(request.PageNumber, request.PageSize);

Even in conjunction with the request verification described earlier, we can add a check rule here:

  • 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.");

Next, request processing is implemented in TodoItemController, where we directly modified the previous page break request to the following (which would result in route discrimination if we added an Action based on the previous one):

  • TodoItemController.cs
// Omit other...
public async Task<ApiResponse<PaginatedList<TodoItemDto>>> GetTodoItemsWithCondition([FromQuery] GetTodoItemsWithConditionQuery query)
    return ApiResponse<PaginatedList<TodoItemDto>>.Success(await _mediator.Send(query));


Start the Api project and execute the request to create a TodoList:

When requesting only Done filter conditions

  • request

  • response

When requesting to carry only the PriorityLevel filter condition

  • request

  • response

When a complete filter condition is requested

  • request

  • response

When request parameters are not valid

  • request

    I passed pageNumber to 0.

  • response


It's still easy to implement for query filtering, but there's an important problem hidden here: if the query has too many parameters, such as field filtering with multiple Guid types, the total length of the URL may exceed the browser or server Host environment limit, in which case, Do we also insist on using GET requests that conform to Level 2, the Richson maturity model?

There has been a lot of debate about this issue. First of all, my personal conclusion is that you can use POST to get around it, and you don't have to be hard on yourself (although this will certainly lead to ridicule from Restful fans). But how do we achieve this if there is a hard requirement for maturity? A more general solution is to split a one-step GET query into two-step GET queries. But more often than not, I think if this happens, give up the Restful style.

actually GraphQL That's the kingdom.

Reference material

  1. Richardson maturity model
  2. GraphQL

Keywords: webapi

Added by jackliu97 on Mon, 03 Jan 2022 07:20:12 +0200