Skip to content

Commit cb6d2b8

Browse files
authored
[GEN-1863] Users pagination (#478)
Add paginated data loading for ApplicationUser repository with server-side pagination support. Implement GetPaginatedAsync method that includes filtering options for banned users and returns both user list and total count for efficient data grid rendering.
1 parent 5fd02d2 commit cb6d2b8

3 files changed

Lines changed: 95 additions & 12 deletions

File tree

src/Data/Repositories/ApplicationUserRepository.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,35 @@ public async Task<List<ApplicationUser>> GetAll(bool includeBanned = false)
357357
return result;
358358
}
359359

360+
public async Task<(List<ApplicationUser> users, int totalCount)> GetPaginatedAsync(
361+
int pageNumber,
362+
int pageSize,
363+
bool includeBanned = false)
364+
{
365+
await using var applicationDbContext = await _dbContextFactory.CreateDbContextAsync();
366+
367+
var query = applicationDbContext.ApplicationUsers
368+
.Include(user => user.Keys)
369+
.Include(user => user.Nodes)
370+
.AsQueryable();
371+
372+
if (!includeBanned)
373+
{
374+
query = query.Where(x => x.LockoutEnd <= DateTimeOffset.UtcNow || x.LockoutEnd == null);
375+
}
376+
377+
query = query.OrderBy(x => x.UserName);
378+
379+
var totalCount = await query.CountAsync();
380+
381+
var users = await query
382+
.Skip((pageNumber - 1) * pageSize)
383+
.Take(pageSize)
384+
.ToListAsync();
385+
386+
return (users, totalCount);
387+
}
388+
360389
public async Task<(bool, string?)> AddAsync(ApplicationUser type, string? password = null)
361390
{
362391
await using var applicationDbContext = await _dbContextFactory.CreateDbContextAsync();

src/Data/Repositories/Interfaces/IApplicationUserRepository.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ public interface IApplicationUserRepository
2929

3030
Task<List<ApplicationUser>> GetAll(bool includeBanned = false);
3131

32+
Task<(List<ApplicationUser> users, int totalCount)> GetPaginatedAsync(
33+
int pageNumber,
34+
int pageSize,
35+
bool includeBanned = false);
36+
3237
Task<(bool, string?)> AddAsync(ApplicationUser type, string? password = null);
3338

3439
Task<(bool, string?)> AddRangeAsync(List<ApplicationUser> type);

src/Pages/Users.razor

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@using Humanizer
22
@using Microsoft.AspNetCore.Identity
33
@using System.Security.Claims
4+
@using System.Threading
45
@using Blazorise.Extensions
56
@using Blazorise
67
@using Blazorise.DataGrid
@@ -11,7 +12,10 @@
1112
<Row>
1213
<Column ColumnSize="ColumnSize.Is12">
1314
<DataGrid TItem="ApplicationUser"
15+
@ref="_usersDataGrid"
1416
Data="@_users"
17+
ReadData="@OnReadData"
18+
TotalItems="@_totalItems"
1519
Editable="true"
1620
EditMode="DataGridEditMode.Popup"
1721
Responsive="true"
@@ -24,7 +28,7 @@
2428
ShowPager="true"
2529
ShowPageSizes="true"
2630
PageSize="25"
27-
Filterable="true"
31+
Filterable="false"
2832
ShowValidationFeedback="true"
2933
ShowValidationsSummary="false"
3034
UseValidation="true">
@@ -56,7 +60,7 @@
5660
</DataGridCommandColumn>
5761
<DataGridColumn TItem="ApplicationUser" Editable="true" Field="@nameof(ApplicationUser.UserName)" Caption="Username" Sortable="false" Displayable="@IsColumnVisible(UsersColumnName.Username)" Filterable="false">
5862
<EditTemplate>
59-
<Validation Validator="@((ValidatorEventArgs obj) => @ValidationHelper.ValidateUsername(obj, _users, context.Item.Id))">
63+
<Validation AsyncValidator="(args, token) => ValidateUsernameAsync(args, token, context.Item.Id)">
6064
<TextEdit Text="@((string)context.CellValue)" TextChanged="(text) => { context.CellValue = text; }">
6165
<Feedback>
6266
<ValidationError/>
@@ -121,6 +125,8 @@
121125
@attribute [Authorize(Roles = "Superadmin")]
122126
@code {
123127
private List<ApplicationUser> _users = new();
128+
private DataGrid<ApplicationUser>? _usersDataGrid;
129+
private int _totalItems;
124130

125131
[CascadingParameter]
126132
private ApplicationUser? LoggedUser { get; set; }
@@ -151,17 +157,26 @@
151157
{
152158
if (LoggedUser != null)
153159
{
154-
await GetData();
160+
await LoadNodesList();
155161
}
156162
}
157163

158-
private async Task GetData()
164+
private async Task LoadNodesList()
159165
{
166+
_nodesList = await NodeRepository.GetAllManagedByNodeGuard();
167+
}
160168

161-
_users = await ApplicationUserRepository.GetAll(true);
169+
private async Task OnReadData(DataGridReadDataEventArgs<ApplicationUser> e)
170+
{
171+
if (LoggedUser == null) return;
162172

163-
_nodesList = await NodeRepository.GetAllManagedByNodeGuard();
173+
var (users, totalCount) = await ApplicationUserRepository.GetPaginatedAsync(
174+
e.Page,
175+
e.PageSize,
176+
includeBanned: true);
164177

178+
_users = users;
179+
_totalItems = totalCount;
165180
}
166181

167182
protected override async Task OnAfterRenderAsync(bool firstRender)
@@ -218,7 +233,10 @@
218233
AuditObjectType.User,
219234
arg.Item.Id,
220235
new { Username = arg.Item.UserName, Roles = string.Join(", ", _selectedRoles) });
221-
await GetData();
236+
if (_usersDataGrid != null)
237+
{
238+
await _usersDataGrid.Reload();
239+
}
222240
}
223241
else
224242
{
@@ -258,7 +276,10 @@
258276
else
259277
{
260278
ToastService.ShowSuccess("Success");
261-
await GetData();
279+
if (_usersDataGrid != null)
280+
{
281+
await _usersDataGrid.Reload();
282+
}
262283
}
263284
}
264285
}
@@ -306,7 +327,10 @@
306327
new { Username = arg.Item.UserName });
307328
}
308329

309-
await GetData();
330+
if (_usersDataGrid != null)
331+
{
332+
await _usersDataGrid.Reload();
333+
}
310334
}
311335

312336
private void NewItemDefaultSetter(ApplicationUser obj)
@@ -390,7 +414,10 @@
390414
new { Username = contextItem.UserName });
391415
}
392416

393-
await GetData();
417+
if (_usersDataGrid != null)
418+
{
419+
await _usersDataGrid.Reload();
420+
}
394421
}
395422
}
396423

@@ -422,8 +449,30 @@
422449
new { Username = contextItem.UserName });
423450
}
424451

425-
await GetData();
426-
} }
452+
if (_usersDataGrid != null)
453+
{
454+
await _usersDataGrid.Reload();
455+
}
456+
}
457+
}
458+
459+
private async Task ValidateUsernameAsync(ValidatorEventArgs obj, CancellationToken cancellationToken, string currentUserId)
460+
{
461+
obj.Status = ValidationStatus.Success;
462+
if (string.IsNullOrWhiteSpace((string)obj.Value))
463+
{
464+
obj.ErrorText = "The Username cannot be empty";
465+
obj.Status = ValidationStatus.Error;
466+
return;
467+
}
468+
469+
var existingUser = await ApplicationUserRepository.GetByUsername(obj.Value.ToString());
470+
if (existingUser != null && existingUser.Id != currentUserId)
471+
{
472+
obj.ErrorText = "A user with the same username already exists";
473+
obj.Status = ValidationStatus.Error;
474+
}
475+
}
427476

428477
private void ValidateRole(ValidatorEventArgs obj)
429478
{

0 commit comments

Comments
 (0)