Пагинация и фильтрация по API
Привет, ребята, сегодня, продолжая серию веб-API ASP NET Core, мы рассмотрим захватывающую тему, а именно разбиение на страницы. Я думаю, что никто из нас не забывает о разбивке на страницы, поскольку почти на любом веб-сайте есть реализация эта функциональность говорит, например, в поиске Google, когда вы ищете что-либо, вы получаете некоторые результаты, и если вы попадаете в нижнюю часть, у вас есть элемент управления, где вы выбираете следующую страницу, чтобы получить больше результатов в случае, если вы не нашли то, что вы были ищем, и это именно то, что мы хотим реализовать в нашем веб-API сегодня.
Что такое пагинация ❓
Разбиение на страницы — это метод, используемый в веб-API для разбиения большого набора данных на более мелкие и более управляемые части. Это достигается за счет ограничения количества результатов, которые мы отправляем обратно на каждый ответ API, и предоставления клиенту возможности запросить следующий набор результатов.
В типичной реализации разбивки на страницы клиент отправляет запрос на сервер, предоставляя определенное количество результатов, которые должны быть возвращены на страницу, а также текущий номер страницы.
Почему мы должны использовать пагинацию в нашем веб-API ⁉️
Разбиение на страницы обычно используется в веб-API для повышения производительности и уменьшения объема данных, которые необходимо передавать между сервером и клиентом, особенно при работе с большими наборами данных. Также улучшает взаимодействие с пользователем, поскольку небольшие фрагменты данных легче усваиваются, чем большие наборы данных, и помогают предотвратить плохое взаимодействие с пользователем при медленном подключении к Интернету, поскольку он не загружает весь набор данных, а только то, что запрошено.
Клонируйте репозиторий GitHub 🐙
Прежде чем приступить к написанию кода, помните, что вы можете следовать руководству, вам просто нужно перейти в репозиторий GitHub и клонировать ветку AsynchronousEndpoints
, чтобы получить последние изменения на данный момент.
GitHub — Osempu/BlogAPI в асинхронных конечных точках
Добавление поддержки параметров запроса в конечную точку Get 🪂
В настоящее время мы вызываем конечную точку Get
и получаем все существующие сообщения из базы данных, так что это отличный пример того, где мы должны реализовать разбивку на страницы, поскольку чем больше растет наша база данных, тем меньше вероятность того, что мы захотим вернуть большой набор данных.
Клиент будет запрашивать количество результатов и номер страницы через строки запроса, и в ASP NET Core есть довольно простой способ справиться с ними, используя конечную точку int атрибута [FromQuery]
. Таким образом, мы указываем ASP NET Core проверить строку запроса, а затем попытаемся прочитать значения из строки запроса и проанализировать их в указанный нами параметр.
[HttpGet] public async Task<IActionResult> GetPost([FromQuery] int pageSize = 50, [FromQuery] int currentPage = 1) { var posts = await repository.GetPostAsync(pageSize, currentPage); //code ommited for brevity }
Мы передали параметры pageSize
и currentPage
, оба украшены
[FromQuery]
, поэтому ASP NET Core пытается прочитать его значения из строки запроса, как мы говорили ранее, и оба параметра имеют значения по умолчанию на случай, если пользователь не предоставит их, а затем мы передаем эти параметры методу GetPostAsync
. Теперь вы должны получить сообщение об ошибке, так как этот метод не принимает никаких параметров, и для этого нам нужно обновить интерфейс IPostRepository
и класс PostRepository
.
Обновление интерфейса IPostRepository
и PostRepositoryClass
📬
Теперь нам нужно обновить наш интерфейс и класс репозитория соответственно для поддержки параметров разбиения на страницы.
Для интерфейса IPostRepository
нам нужно определить только два параметра pageSize
и pageNumber
.
public interface IPostRepository { Task<IEnumerable<Post>> GetPostAsync(int pageSize, int pageNumbe); //Code omitted for brevity }
А для почтового репозитория мы реализуем сигнатуру интерфейса, поддерживающую те же параметры. Теперь все, что осталось, это реализовать функцию разбиения на страницы.
public async Task<IEnumerable<Post>> GetPostAsync(int pageSize, int pageNumber) { //Code omitted for brevity }
Реализация пагинации в классе PostRepository
🏤
public async Task<IEnumerable<Post>> GetPostAsync(int pageSize, int pageNumber) { var allPosts = context.Posts.AsQueryable(); var pagedPosts = await allPosts .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .ToListAsync(); return pagedPosts; }
У вас может возникнуть соблазн сделать это в Post-контроллере после получения всех сообщений, но идея разбивки на страницы заключается в том, чтобы ограничить количество ресурсов, возвращаемых базой данных, и это необходимо сделать в классе репозитория, потому что именно там мы вызываем базу данных.
Во-первых, мы делаем вызов ко всем сообщениям, но как запрашиваемый тип, это означает, что мы запрашиваем все сообщения, существующие в базе данных, но запрос LINQ еще не был выполнен, это помогает нам экономить ресурсы, поскольку мы все еще строя запрос, затем мы делаем еще один запрос ко всем сообщениям. Мы вызываем метод skip, чтобы пропустить предыдущие страницы, если клиент просит просмотреть, например, страницу 3 или 8, затем мы берем определенное количество сообщений для показа пользователю, это указывается параметром pageSize
, и, наконец, мы вызовите метод ToListAsync()
, чтобы выполнить запрос и вернуть список сообщений.
Теперь вы можете запустить приложение и начать тестирование нумерации страниц. Если вы используете Swagger, вы увидите два новых параметра, которые являются параметрами запроса, которые вы можете ввести для pageSize
и pageNumber
, и когда вы выполняете вызов, URL-адрес запроса должен выглядеть одинаково. к этому
https://localhost:5049/api/post?pageSize=10¤tPage=1
так что вы должны получать 10 сообщений с первой страницы.
Проблема с этим подходом 💥
Вы, должно быть, думаете, что это все, и мы можем двигаться дальше, верно? ну, спойлер, мы не можем. Дело в том, что это решение плохо масштабируется, так как нам нужно предоставить некоторые значения по умолчанию для параметров запроса в случае, если пользователь их не предоставляет, хотя мы можем сделать это для параметров, представьте, что позже мы хотим фильтровать в нашем поиске это еще один параметр, который нужно передать, не думайте о сортировке, в конечном итоге у нашей конечной точки будет много параметров для установки, и мы знаем, что это не что-то хорошее и не масштабируемое, поэтому нам нужно реорганизовать наше решение, чтобы сделать его более масштабируемым для будущих изменений .
Давайте создадим новый класс модели с именем QueryParameters
, этот класс будет содержать все параметры, необходимые для разбивки на страницы и для будущих изменений в нашем API.
public class QueryParameters { const int maxPageSize = 50; public int PageNumber { get; set; } = 1; private int pageSize = 10; public int PageSize { get { return pageSize; } set { pageSize = (value > maxPageSize) ? maxPageSize : value; } } }
Теперь с помощью этого класса мы будем хранить все параметры, необходимые для работы нумерации страниц и будущей функциональности. Мы определяем максимальный размер страницы, мы устанавливаем для страницы значение по умолчанию 1 в случае, если пользователь не предоставляет страницу для отображения, и, наконец, мы устанавливаем размер страницы на значение по умолчанию 10 или любое другое значение, заданное пользователем. определяет.
Внедряем новый подход 🆕
[HttpGet] public async Task<IActionResult> GetPost([FromQuery] QueryParameters parameters) { var posts = await repository.GetPostAsync(parameters.PageSize, parameters.PageNumber); var postsDto = mapper.Map<IEnumerable<PostResponseDTO>>(posts); logger.LogDebug($"Get method called, got {postsDto.Count()} results"); return Ok(postsDto); }
Если вы запустите приложение, оно будет работать, как и раньше, но теперь у нас есть улучшенная версия, когда мы добавляем фильтрацию и другие параметры для улучшения нашей конечной точки.
Вывод 🌇
Разбивка на страницы — это мощный метод, используемый для улучшения взаимодействия с пользователем при одновременной экономии ресурсов сервера, поскольку мы не возвращаем все доступные данные, избегая узких мест и проблем с производительностью при медленных соединениях. Как и все техники программирования, пейджинг нужно использовать с умом, так как не в каждом проекте есть смысл его применять, например, в сервисе аналитики, в таком контексте имеет смысл возвращать сотни, если не тысячи записей, так как чем больше данных у вас под рукой, тем более надежным и точным становится этот сервис.