-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathPaginationService.cs
More file actions
118 lines (97 loc) · 4.2 KB
/
PaginationService.cs
File metadata and controls
118 lines (97 loc) · 4.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
namespace Common;
using Microsoft.Azure.Functions.Worker.Http;
public class PaginationService<T> : IPaginationService<T>
{
private const int pageSize = 10;
public PaginationResult<T> GetPaginatedResult(IQueryable<T> source, int? lastId, Func<T, int>? idSelector = null)
{
// If no idSelector is provided, try to use a default 'Id' property
if (idSelector == null)
{
idSelector = GetDefaultIdSelector();
}
// Convert source to a list of IDs to calculate index-based pagination
var idList = source.Select(idSelector).OrderBy(id => id).ToList();
// Get the index of the lastId
int lastIdIndex = lastId.HasValue ? idList.IndexOf(lastId.Value) : -1;
int currentPage = lastIdIndex >= 0 ? (lastIdIndex / pageSize) + 2 : 1;
var totalItems = source.Count();
var totalPages = (int)Math.Ceiling((double)totalItems / pageSize);
if (lastIdIndex >= 0)
{
source = source.Skip(lastIdIndex + 1);
}
var items = source.Take(pageSize).ToList();
int? lastResultId = items.Count > 0 ? idSelector(items[^1]) : null;
return new PaginationResult<T>
{
Items = items,
IsFirstPage = currentPage == 1,
HasNextPage = lastResultId.HasValue && (lastIdIndex + pageSize < totalItems),
LastResultId = lastResultId,
TotalItems = totalItems,
TotalPages = totalPages,
CurrentPage = currentPage,
};
}
/// <summary>
/// Adds pagination navigation headers to the response.
/// </summary>
public Dictionary<string, string> AddNavigationHeaders<TEntity>(HttpRequestData request, PaginationResult<TEntity> paginationResult)
{
var headers = new Dictionary<string, string>
{
["X-Total-Count"] = paginationResult.TotalItems.ToString(),
["X-Has-Next-Page"] = paginationResult.HasNextPage.ToString().ToLower(),
["X-Is-First-Page"] = paginationResult.IsFirstPage.ToString().ToLower()
};
if (paginationResult.LastResultId.HasValue)
{
headers["X-Last-Id"] = paginationResult.LastResultId.Value.ToString();
}
var linkHeaders = BuildLinkHeaders(request, paginationResult);
if (linkHeaders.Count > 0)
{
headers["Link"] = string.Join(", ", linkHeaders);
}
return headers;
}
private static List<string> BuildLinkHeaders<TEntity>(HttpRequestData request, PaginationResult<TEntity> paginationResult)
{
var linkHeaders = new List<string>();
var baseUrl = request.Url.GetLeftPart(UriPartial.Path);
var queryString = request.Url.Query;
var baseQuery = RemoveLastIdParam(queryString);
var separator = string.IsNullOrEmpty(baseQuery) ? "?" : "&";
// First page link (no lastId)
linkHeaders.Add($"<{baseUrl}{baseQuery}>; rel=\"first\"");
// Next page link (only if has next page)
if (paginationResult.HasNextPage && paginationResult.LastResultId.HasValue)
{
linkHeaders.Add($"<{baseUrl}{baseQuery}{separator}lastId={paginationResult.LastResultId.Value}>; rel=\"next\"");
}
return linkHeaders;
}
private static string RemoveLastIdParam(string queryString)
{
if (string.IsNullOrEmpty(queryString)) return "";
var pairs = queryString.TrimStart('?').Split('&')
.Where(p => !p.StartsWith("lastId="))
.Where(p => !string.IsNullOrWhiteSpace(p))
.ToArray();
return pairs.Length > 0 ? "?" + string.Join("&", pairs) : "";
}
private static Func<T, int> GetDefaultIdSelector()
{
var idProperty = (typeof(T).GetProperty("Id") ??
typeof(T).GetProperty($"{typeof(T).Name}Id")) ??
throw new InvalidOperationException(
"Could not find a default ID property. Provide a custom ID selector.");
return x =>
{
var value = idProperty.GetValue(x) ?? throw new InvalidOperationException(
$"Entity of type {typeof(T).Name} has a null ID property ('{idProperty.Name}').");
return (int)value;
};
}
}