Академический Документы
Профессиональный Документы
Культура Документы
Исходный код этой статьи можно найти в репозитории GitHub . Если вы хотите следовать этой статье, вы можете использовать стартовую
ветвь и, если вы хотите получить окончательное решение или если вы застряли, переключитесь на конечную ветвь .
ПРИМЕЧАНИЕ : некоторая степень предыдущих знаний необходима, чтобы следовать этой статье. Он в значительной степени опирается
на серии ASP.NET Core Web API на Code Maze, поэтому, если вы не уверены, как настроить базу данных или как работает базовая архитектура
, мы настоятельно рекомендуем вам пройти серию.
Мы обсудим, что такое подкачка страниц, самый простой способ ее реализации, а затем усовершенствуем это решение, чтобы создать
более удобочитаемую и гибкую кодовую базу.
Начальная реализация
Пейджинговая реализация
Тестирование решения
Улучшение решения
Давайте начнем.
Мало того, что это будет крайне неэффективным способом возврата результатов, но это также может иметь разрушительные
последствия для самого приложения или оборудования, на котором оно работает . Кроме того, каждый клиент имеет ограниченные
ресурсы памяти, и ему необходимо ограничить количество отображаемых результатов.
Таким образом, нам нужен способ вернуть определенное количество результатов клиенту, чтобы избежать этих последствий.
Начальная реализация
Прежде чем вносить какие-либо изменения в исходный код, давайте проверим, как он выглядит прямо сейчас и как вы, вероятно, начнете
с любого проекта.
В нашем случае мы имеем OwnerController все необходимые действия над Owner объектом.
Одно конкретное действие, которое выделяется и которое нам нужно изменить, - это GetOwners() действие:
OwnerRepository.cs C#
1 public IEnumerable<Owner> GetOwners()
2 {
3 return FindAll()
4 .OrderBy(ow => ow.Name);
5 }
Метод FindAll () - это просто метод из базового класса репозитория, который возвращает весь набор владельцев.
1 public IQueryable<T> FindAll()
2 {
3 return this.RepositoryContext.Set<T>();
4 }
Как видите, это простое действие, предназначенное для возврата всех владельцев из базы данных, упорядоченной по имени.
Но в нашем случае это всего лишь несколько владельцев аккаунтов (пять). Что делать, если в базе данных были тысячи или даже
миллионы людей (вы хотите, но все же, представьте себе другую сущность). Конец, а затем добавить к этому, несколько тысяч
пользователей API.
В лучшем случае вы начинаете с небольшого числа владельцев, число которых постепенно увеличивалось, чтобы вы могли заметить
медленное снижение производительности. Другие сценарии гораздо менее благоприятны для вашего приложения и компьютеров
(представьте, что вы размещаете его в облаке и не располагаете надлежащим кэшированием).
Итак, имея это в виду, давайте изменим этот метод для поддержки подкачки страниц.
Пейджинговая реализация
Имейте в виду, мы не хотим изменять базовую логику хранилища или реализовывать какую-либо бизнес-логику в контроллере.
То, что мы хотим достичь, это что-то вроде этого https://localhost:5001/api/owners?pageNumber=2&pageSize=2 . Это должно
вернуть второй набор из двух владельцев, которые мы имеем в нашей базе данных.
Мы также хотим ограничить наш API, чтобы не возвращать всех владельцев, даже если кто-то звонит
https://localhost:5001/api/owners .
Мы вызываем GetOwners метод из OwnerRepository , который еще не существует, но скоро мы его реализуем
Мы используем, [FromQuery] чтобы указать, что мы будем использовать параметры запроса, чтобы определить, какую страницу
и сколько владельцев мы запрашиваем
Нам также нужно создать OwnerParameters класс, поскольку мы передаем его в качестве аргумента нашему контроллеру. Давайте
создадим его в папке Models проекта Entities:
OwnerParameters.cs C#
1 public class OwnerParameters
2 {
3 const int maxPageSize = 50;
4 public int PageNumber { get; set; } = 1;
5
6 private int _pageSize = 10;
7 public int PageSize
8 {
9 get
10 {
11 return _pageSize;
12 }
13 set
14 {
15 _pageSize = (value > maxPageSize) ? maxPageSize : value;
16 }
17 }
18 }
Мы используем константу, maxPageSize чтобы ограничить наш API максимум 50 владельцами. У нас есть два открытых свойства -
PageNumber и PageSize. Если вызывающий не установил, PageNumber будет установлен в 1, а PageSize в 10.
IOwnerRepository.cs C#
1 public interface IOwnerRepository : IRepositoryBase<Owner>
2 {
3 IEnumerable<Owner> GetOwners(OwnerParameters ownerParameters);
4 Owner GetOwnerById(Guid ownerId);
5 OwnerExtended GetOwnerWithDetails(Guid ownerId);
6 void CreateOwner(Owner owner);
7 void UpdateOwner(Owner dbOwner, Owner owner);
8 void DeleteOwner(Owner owner);
9 }
И логика:
OwnerRepository,cs C#
1 public IEnumerable<Owner> GetOwners(OwnerParameters ownerParameters)
2 {
3 return FindAll()
4 .OrderBy(on => on.Name)
5 .Skip((ownerParameters.PageNumber - 1) * ownerParameters.PageSize)
6 .Take(ownerParameters.PageSize)
7 .ToList();
8 }
Тестирование решения
Теперь в нашей базе данных только несколько владельцев, поэтому давайте попробуем что-то вроде этого:
https://localhost:5001/api/owners?pageNumber=2&pageSize=2
Улучшение решения
Поскольку мы возвращаем вызывающей стороне только подмножество результатов, мы могли бы иметь PagedList вместо List .
PagedList будет наследовать от List класса и добавит еще немного к нему. Мы также можем переместить логику пропуска / принятия в
PagedList, так как это имеет больше смысла.
PagedList.cs C#
1 public class PagedList<T> : List<T>
2 {
3 public int CurrentPage { get; private set; }
4 public int TotalPages { get; private set; }
5 public int PageSize { get; private set; }
6 public int TotalCount { get; private set; }
7
8 public bool HasPrevious => CurrentPage > 1;
9 public bool HasNext => CurrentPage < TotalPages;
10
11 public PagedList(List<T> items, int count, int pageNumber, int pageSize)
12 {
13 TotalCount = count;
14 PageSize = pageSize;
15 CurrentPage = pageNumber;
16 TotalPages = (int)Math.Ceiling(count / (double)pageSize);
17
18 AddRange(items);
19 }
20
21 public static PagedList<T> ToPagedList(IEnumerable<T> source, int pageNumber, int pageSize)
22 {
23 var count = source.Count();
24 var items = source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToList();
25
26 return new PagedList<T>(items, count, pageNumber, pageSize);
27 }
28 }
Как видите, мы передали логику пропуска / принятия статическому методу внутри PagedList класса. Мы добавили еще несколько
свойств, которые пригодятся в качестве метаданных для нашего ответа.
HasPrevious Значение true, если CurrentPage больше 1, и HasNext рассчитывается, если CurrentPage меньше, чем общее
количество страниц. TotalPages рассчитывается также путем деления количества элементов на размер страницы и последующего
округления до большего числа, поскольку страница должна существовать, даже если на ней есть один элемент.
Теперь, когда мы это выяснили, давайте изменим наше OwnerRepository и OwnerController соответственно.
Во-первых, нам нужно изменить репо (не забудьте также изменить интерфейс):
OwnerRepository.cs C#
1 public PagedList<Owner> GetOwners(OwnerParameters ownerParameters)
2 {
3 return PagedList<Owner>.ToPagedList(FindAll().OrderBy(on => on.Name),
4 ownerParameters.PageNumber,
5 ownerParameters.PageSize);
6 }
И тогда контроллер:
OwnerController.cs C#
1 [HttpGet]
2 public IActionResult GetOwners([FromQuery] OwnerParameters ownerParameters)
3 {
4 var owners = _repository.Owner.GetOwners(ownerParameters);
5
6 var metadata = new
7 {
8 owners.TotalCount,
9 owners.PageSize,
owners.CurrentPage,
10
11 owners.TotalPages,
12 owners.HasNext,
13 owners.HasPrevious
};
14
15
Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(metadata));
16
17
_logger.LogInfo($"Returned {owners.TotalCount} owners from database.");
18
19
return Ok(owners);
20
}
21
Как видите, все наши метаданные здесь. Мы можем использовать эту информацию при создании любого вида функциональности
нумерации веб-интерфейса. Вы можете поиграться с различными запросами, чтобы увидеть, как это работает в других сценариях.
Есть еще одна вещь, которую мы можем сделать, чтобы сделать наше решение еще более общим. У нас есть OwnerParameters класс, но
что, если мы хотим использовать его в нашем AccountController ? Параметры, которые мы отправляем контроллеру аккаунта, могут
отличаться. Возможно, не для подкачки страниц, но позже мы отправим несколько различных параметров, и нам нужно разделить классы
параметров.
Давайте начнем с определения QueryStringParameters класса внутри папки Models проекта Entities:
QueryStringParameters.cs C#
1 public abstract class QueryStringParameters
2 {
3 const int maxPageSize = 50;
4 public int PageNumber { get; set; } = 1;
5
6 private int _pageSize = 10;
7 public int PageSize
8 {
9 get
10 {
11 return _pageSize;
12 }
13 set
14 {
15 _pageSize = (value > maxPageSize) ? maxPageSize : value;
16 }
17 }
18 }
Мы также переместили нашу логику подкачки в класс, так как она будет действительна для любой сущности, которую мы можем захотеть
вернуть через репозиторий.
Теперь нам нужно создать AccountParameters класс, а затем наследовать его в QueryStringParameters классах OwnerParameters и
AccountParameters.
OwnerParameters.cs C#
1 public class OwnerParameters : QueryStringParameters
2 {
3
4 }
AccountParameters.cs C#
1 public class AccountParameters : QueryStringParameters
2 {
3
4 }
Теперь эти классы выглядят немного пустыми, но скоро мы наполним их другими полезными параметрами и посмотрим, какова реальная
выгода. Сейчас важно, чтобы у нас был способ отправить другой набор параметров для AccountController и OwnerController .
C#
1 [HttpGet]
2 public IActionResult GetAccountsForOwner(Guid ownerId, [FromQuery] AccountParameters parameters)
3 {
4 var accounts = _repository.Account.GetAccountsByOwner(ownerId, parameters);
5
6 var metadata = new
7 {
8 accounts.TotalCount,
9 accounts.PageSize,
10 accounts.CurrentPage,
11 accounts.TotalPages,
12 accounts.HasNext,
13 accounts.HasPrevious
14 };
15
16 Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(metadata));
17
18 _logger.LogInfo($"Returned {accounts.TotalCount} owners from database.");
19
20 return Ok(accounts);
21 }
И благодаря наследованию параметров подкачки через QueryStringParameters класс мы получаем то же самое поведение. Ухоженная.
Вывод
Пейджинг - это полезная и важная концепция для создания любого API. Без этого наше приложение, вероятно, значительно замедлится
или просто замертво.
Решение, которое мы реализовали, далеко не идеальное, но вы поняли. Мы выделили различные части механизма подкачки, и мы можем
пойти еще дальше и сделать его более общим. Но вы можете сделать это как упражнение и реализовать его в своем собственном
проекте. Вы также можете найти одно из применений пейджинга в нашей статье .
Самый простой способ реализовать нумерацию страниц в ASP.NET Core Web API
Улучшил это решение, введя PagedList сущность и разделив наши параметры для разных контроллеров
Надеюсь, вам понравилась эта статья и вы узнали что-то новое или полезное из нее.
Если вам понравилось читать эту статью и вы хотели бы получать уведомления о недавно опубликованном контенте .NET Core, мы
призываем вас подпишитесь на наш блог.