سلام خدمت استاد عزیز و همه دوستان
بنده جلسات گفته شده را ریفکتور کرده ام. یه خورده طولانی هست. ممنون میشم تحمل بفرمایید
ابتدا در کلاس BasePaging (پوشه ShopStock.Domain.ViewModels.Common) به کلاس PageingViewModel ، فیلد PageCount را اضافه کرده ام که مقدارش رو در متد GetCurrentPaging کلاس BasePaging پرکرده ام.
public PageingViewModel GetCurrentPaging()
{
return new PageingViewModel()
{
PageId = this.PageId,
StartPage = this.StartPage,
EndPage = this.EndPage,
PageCount = this.PageCount
};
}. از این فیلد برای پیمایش به صفحه ی آخر ِ آخر کاربران استفاده شده . در پارشیال _AdminPagination.cshtml (پوشه shared اریای Admin) کد های مربوط به صفحه بندی را به این صورت تغییر دادم که کاربر بتواند در هر صورت به صفحه ی 1 و یا اخر دسترسی داشته باشد.
@using ShopStock.Domain.ViewModels.Common
@model PageingViewModel
@if (Model.PageCount>1)
{
<div class="d-flex justify-content-center align-items-center py-2">
<nav aria-label="Page navigation">
<ul class="pagination pagination-primary m-0">
<li class="page-item first @(Model.PageId == 1 ? "disabled" : "")">
<a class="page-link" href="@ViewBag.Url?PageId=1"><i class="tf-icon bx bx-chevrons-left"></i></a>
</li>
<li class="page-item prev @(Model.PageId == 1 ? "disabled" : "")">
<a class="page-link" href="@ViewBag.Url?PageId=@(Model.PageId - 1)"><i class="tf-icon bx bx-chevron-left"></i></a>
</li>
@for (int i = Model.StartPage; i <= Model.EndPage; i++)
{
<li class='page-item @((Model.PageId == i) ? "active" : "")'>
<a class="page-link" href="@ViewBag.Url?PageId=@i">@i</a>
</li>
}
<li class="page-item next @(Model.PageId == Model.PageCount ? "disabled" : "")">
<a class="page-link" href="@ViewBag.Url?PageId=@(Model.PageId + 1)"><i class="tf-icon bx bx-chevron-right"></i></a>
</li>
<li class="page-item last @(Model.PageId == Model.PageCount ? "disabled" : "")">
<a class="page-link" href="@ViewBag.Url?PageId=@(Model.PageCount)"><i class="tf-icon bx bx-chevrons-right"></i></a>
</li>
</ul>
</nav>
</div>
}تصویر خروجی بالا :

در ویومدل AdminFilterUserViewModel.cs ( پوشه ShopStock.Domain.ViewModels.UserVM) که همان مدل صفحه ی ایندکس کاربران خواهد شد برای فیلد های IsEmailActive و IsMobileActive و IsActive و IsDeleted به صورت جدا برای هرکدوم Enum هایی تعریف شده است تا کاربر بتواند روی این فیلد ها فیلتر انجام دهد.
در پوشه ShopStock.Domain.Enums.User اینام های زیر را تعریف کرده ام :
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Text;
namespace ShopStock.Domain.Enums.User
{
public enum FilterActiveStatus
{
[Display(Name = "فعال")] Active,
[Display(Name="همه")] All,
[Display(Name = "غیرفعال")] NotActive
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Text;
namespace ShopStock.Domain.Enums.User
{
public enum FilterDeleteStatus
{
[Display(Name="حذف نشده")] NotDeleted,
[Display(Name = "همه")] All,
[Display(Name = "حذف شده")] Deleted
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Text;
namespace ShopStock.Domain.Enums.User
{
public enum FilterEmailStatus
{
[Display(Name = "فعال")] Active,
[Display(Name="همه")] All,
[Display(Name = "غیرفعال")] NotActive
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Text;
namespace ShopStock.Domain.Enums.User
{
public enum FilterMobileStatus
{
[Display(Name = "فعال")] Active,
[Display(Name="همه")] All,
[Display(Name = "غیرفعال")] NotActive
}
}
در ویومدل AdminFilterUserViewModel.cs ( پوشه ShopStock.Domain.ViewModels.UserVM) این فیلدها را قرار داده ام و برای هرکدام از Enum های بالا مقدار پیشفرض قرار داده ام :
using ShopStock.Domain.Enums.User;
using ShopStock.Domain.ViewModels.Common;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.SqlTypes;
using System.Text;
namespace ShopStock.Domain.ViewModels.UserVM
{
public class FilteredUserListForAdminViewModel : BasePaging<UserListItemForAdminViewModel>
{
#region Properties
[DisplayName("شناسه کاربر")]
public int UserId { get; set; }
[DisplayName("نام")]
public string? FirstName { get; set; }
[DisplayName("نام خانوادگی")]
public string? LastName { get; set; }
[DisplayName("نام کاربری")]
public string? UserName { get; set; }
[DisplayName("ایمیل")]
public string? Email { get; set; }
[DisplayName("موبایل")]
public string? Mobile { get; set; }
[DisplayName("کد ملی")]
public string? NationalCode { get; set; }
//public DateTime? FromCreateDate { get; set; }
//[DisplayName("از تاریخ ثبت نام:")]
//public string? FromPcCreateDate { get; set; }
//public DateTime? ToCreateDate { get; set; }
//[DisplayName("تا تاریخ ثبت نام:")]
//public string? ToPcCreateDate { get; set; }
[DisplayName("وضعیت ایمیل")]
public FilterEmailStatus? EmailStatus { get; set; } = FilterEmailStatus.Active;
[DisplayName("وضعیت موبایل")]
public FilterMobileStatus? MobileStatus { get; set; } = FilterMobileStatus.Active;
[DisplayName("وضعیت فعال بودن")]
public FilterActiveStatus? ActiveStatus { get; set; } = FilterActiveStatus.Active;
[DisplayName("وضعیت حذف")]
public FilterDeleteStatus? DeleteStatus { get; set; } = FilterDeleteStatus.NotDeleted;
#endregion
}
}
در تعریف کلاس بالا ،
public class FilteredUserListForAdminViewModel : BasePaging<UserListItemForAdminViewModel>UserListItemForAdminViewModel (در پوشه ShopStock.Domain.ViewModels.UserVM) همان کلاسی است که قراره در فیلد Entities از این جنس باشه که در ویوی ایندکس فیلد Entities فورایچ خواهد خورد و دیتای مربوط به هر کاربر نشون داده میشه .
کد مربوط به کلاس UserListItemForAdminViewModel به صورت زیر است:
using ShopStock.Domain.Attributes;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Text;
namespace ShopStock.Domain.ViewModels.UserVM
{
public class UserListItemForAdminViewModel
{
#region Properties
[DisplayName("شناسه کاربر")]
public int UserId { get; set; }
[DisplayName("تصویر کاربر")]
public string? Avatar { get; set; }
[DisplayName("نام")]
public string? FirstName { get; set; }
[DisplayName("نام خانوادگی")]
public string? LastName { get; set; }
[DisplayName("نام کاربری")]
public string UserName { get; set; }
[DisplayName("ایمیل")]
public string Email { get; set; }
[DisplayName("ایمیل فعال؟")]
public bool IsEmailActive { get; set; }
[DisplayName("موبایل")]
public string? Mobile { get; set; }
[DisplayName("موبایل فعال؟")]
public bool IsMobileActive { get; set; }
[DisplayName("کد ملی")]
public string? NationalCode { get; set; }
[DisplayName("تاریخ ثبت نام")]
public DateTime CreateDate { get; set; }
[DisplayName("کاربر فعال است؟")]
public bool IsActive { get; set; }
[DisplayName("کاربر حذف شده؟")]
public bool IsDeleted { get; set; }
#endregion
}
}
در سرویس کاربران ، متد فیلتر کاربران ListOfFilteredUsersForAdminAsync هست:
(در پوشه ShopStock.Application.Services.Implementation)
public async Task<BasePaging<UserListItemForAdminViewModel>> ListOfFilteredUsersForAdminAsync(FilteredUserListForAdminViewModel? userFilter)
{
#region Query
IQueryable<User> query = await _userRepository.GetFilteredUsersForAdminAsync();
if (query == null)
{
//throw new Exception("هیچ کاربری در دیتابیس یافت نشد");
return null;
}
#endregion
#region Filter
if (userFilter.UserId != null &amp;&amp; userFilter.UserId != 0) // در صورتی که کاربر بخواهد با توجه به آیدی فقط یک کاربر مشخص را بگیرد
{
query = query.Where(u => u.Id == userFilter.UserId);
}
else
{
if (!string.IsNullOrEmpty(userFilter.FirstName))
{
query = query.Where(u => u.FirstName.Contains(userFilter.FirstName.Trim()));
}
if (!string.IsNullOrEmpty(userFilter.LastName))
{
query = query.Where(u => u.LastName.Contains(userFilter.LastName.Trim()));
}
if (!string.IsNullOrEmpty(userFilter.UserName))
{
query = query.Where(u => u.UserName.Contains(userFilter.UserName.Trim()));
}
if (!string.IsNullOrEmpty(userFilter.Email))
{
query = query.Where(u => u.Email.Contains(userFilter.Email.FixEmail()));
}
if (!string.IsNullOrEmpty(userFilter.Mobile))
{
query = query.Where(u => u.Mobile.Contains(userFilter.Mobile.Trim()));
}
if (!string.IsNullOrEmpty(userFilter.NationalCode))
{
query = query.Where(u => u.NationalCode.Contains(userFilter.NationalCode.Trim()));
}
//if (userFilter.FromCreateDate != null)
//{
// query = query.Where(u => u.CreateDate >= userFilter.FromCreateDate);
//}
//if (userFilter.ToCreateDate != null)
//{
// query = query.Where(u => u.CreateDate <= userFilter.ToCreateDate);
//}
if (userFilter.EmailStatus != null)
{
switch (userFilter.EmailStatus)
{
case FilterEmailStatus.Active:
{
query = query.Where(u => u.IsEmailActive == true);
break;
}
case FilterEmailStatus.All:
{
break;
}
case FilterEmailStatus.NotActive:
{
query = query.Where(u => u.IsEmailActive == false);
break;
}
}
}
if (userFilter.MobileStatus != null)
{
switch (userFilter.MobileStatus)
{
case FilterMobileStatus.Active:
{
query = query.Where(u => u.IsMobileActive == true);
break;
}
case FilterMobileStatus.All:
{
break;
}
case FilterMobileStatus.NotActive:
{
query = query.Where(u => u.IsMobileActive == false);
break;
}
}
}
if (userFilter.ActiveStatus != null)
{
switch (userFilter.ActiveStatus)
{
case FilterActiveStatus.Active:
{
query = query.Where(u => u.IsActive == true);
break;
}
case FilterActiveStatus.All:
{
break;
}
case FilterActiveStatus.NotActive:
{
query = query.Where(u => u.IsActive == false);
break;
}
}
}
if (userFilter.DeleteStatus != null)
{
switch (userFilter.DeleteStatus)
{
case FilterDeleteStatus.NotDeleted:
{
query = query.Where(u => u.IsDeleted == false);
break;
}
case FilterDeleteStatus.All:
{
break;
}
case FilterDeleteStatus.Deleted:
{
query = query.Where(u => u.IsDeleted == true);
break;
}
}
}
}
#endregion
#region Sort
query = query.OrderByDescending(u => u.CreateDate);
#endregion
#region Mapping
IQueryable<UserListItemForAdminViewModel> mappedQuery = query.Select(u => UserMapper.MapUserToUserListItemForAdminViewModel(u));
return await userFilter.Paging(mappedQuery);
#endregion
}
در اکشن Index کنترل یوزرز (پوشه ShopStock.Web.Areas.Admin.Controllers) :
public async Task<IActionResult> Index(FilteredUserListForAdminViewModel? userFilter)
{
var filteredUser = await _userService.ListOfFilteredUsersForAdminAsync(userFilter);
return View(filteredUser);
}
کد های مربوط به ویوی Index :
@model FilteredUserListForAdminViewModel
@{
ViewData["Title"] = "لیست کاربران";
ViewBag.Url = "/Admin/Users";
}
<div class="accordion" id="accordionExample">
<div class="card shadow-sm accordion-item">
<h4 class="accordion-header" id="headingOne">
<button type="button" class="accordion-button collapsed" data-bs-toggle="collapse" data-bs-target="#accordionOne" aria-expanded="false" aria-controls="accordionOne">
فیلتر
</button>
</h4>
<div id="accordionOne" class="accordion-collapse collapse" data-bs-parent="#accordionExample" style="">
<div class="accordion-body">
<form asp-action="Index" id="filterForm">
<input type="hidden" name="PageId" value="@Model.PageId" />
<div class="row g-3">
<div class="col-md-6">
<div class="form-group">
<label asp-for="UserId" class="form-label">آیدی</label>
<input asp-for="UserId" class="form-control" />
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label asp-for="FirstName" class="form-label">نام</label>
<input asp-for="FirstName" class="form-control" />
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label asp-for="LastName" class="form-label">نام خانوادگی</label>
<input asp-for="LastName" class="form-control" />
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label asp-for="UserName" class="form-label">نام کاربری</label>
<input asp-for="UserName" class="form-control" />
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label asp-for="Email" class="form-label">ایمیل</label>
<input asp-for="Email" class="form-control" />
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label asp-for="Mobile" class="form-label">موبایل</label>
<input asp-for="Mobile" class="form-control" />
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label asp-for="NationalCode" class="form-label">کد ملی</label>
<input asp-for="NationalCode" class="form-control" />
</div>
</div>
</div>
<div class="row g-3 my-3">
<div class="col-md-3">
<div class="form-group">
<label asp-for="EmailStatus" class="form-label"></label>
<select asp-for="EmailStatus" class="form-control" asp-items="Html.GetEnumSelectList<ShopStock.Domain.Enums.User.FilterEmailStatus>()">
</select>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label asp-for="MobileStatus" class="form-label"></label>
<select asp-for="MobileStatus" class="form-control" asp-items="Html.GetEnumSelectList<ShopStock.Domain.Enums.User.FilterMobileStatus>()">
</select>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label asp-for="ActiveStatus" class="form-label"></label>
<select asp-for="ActiveStatus" class="form-control" asp-items="Html.GetEnumSelectList<ShopStock.Domain.Enums.User.FilterActiveStatus>()">
</select>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label asp-for="DeleteStatus" class="form-label"></label>
<select asp-for="DeleteStatus" class="form-control" asp-items="Html.GetEnumSelectList<ShopStock.Domain.Enums.User.FilterDeleteStatus>()">
</select>
</div>
</div>
</div>
@* <div class="row g-3">
<div class="col-md-6">
<div class="form-group">
@Html.LabelFor(Model => Model.FromPcCreateDate, new { @class = "form-label" })
@Html.TextBoxFor(Model => Model.FromPcCreateDate, new { @class = "form-control", @id = "FromPcCreateDate", @readonly = "readonly" })
</div>
</div>
<div class="col-md-6">
<div class="form-group">
@Html.LabelFor(Model => Model.ToPcCreateDate, new { @class = "form-label" })
@Html.TextBoxFor(Model => Model.ToPcCreateDate, new { @class = "form-control", @id = "ToPcCreateDate", @readonly = "readonly" })
</div>
</div>
</div> *@
<div class="mt-4 d-flex gap-2">
<button type="submit" class="btn btn-success">
فیلتر
</button>
<a asp-action="Index" class="btn btn-outline-warning">
پاک کردن فیلترها
</a>
</div>
</form>
</div>
</div>
</div>
</div>
<hr />
<div class="card shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">@ViewData["Title"]</h5>
<a class="btn btn-success btn-sm" asp-action="Create">
<i class="bi bi-plus-lg"></i> افزودن کاربر
</a>
</div>
<div class="table-responsive">
<table class="table table-sm table-hover table-bordered align-middle text-nowrap mb-0">
<thead class="table-light">
<tr class="text-center">
<th>تصویر</th>
<th>نام</th>
<th>نام خانوادگی</th>
<th>نام کاربری</th>
<th>ایمیل</th>
<th>ایمیل</th>
<th>موبایل</th>
<th>موبایل</th>
<th>کد ملی</th>
<th>تاریخ ایجاد</th>
<th>وضعیت</th>
<th>حذف شده</th>
<th class="text-center">عملیات</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Entities)
{
string rowClass = item.IsDeleted ? "table-danger" : "";
<tr class="@rowClass">
<td class="text-center">
<img class="rounded-circle border"
width="45"
height="45"
src="@(item.Avatar != null ? "/Avatars/" + item.Avatar : "/Avatars/NoPhoto.jpg")">
</td>
<td>@item.FirstName</td>
<td>@item.LastName</td>
<td>@item.UserName</td>
<td>@item.Email</td>
<td class="text-center">
@if (item.IsEmailActive)
{
<span class="badge bg-success">فعال</span>
}
else
{
<span class="badge bg-secondary">غیرفعال</span>
}
</td>
<td>@item.Mobile</td>
<td class="text-center">
@if (item.IsMobileActive)
{
<span class="badge bg-success">فعال</span>
}
else
{
<span class="badge bg-secondary">غیرفعال</span>
}
</td>
<td>@item.NationalCode</td>
<td>
<small class="text-muted">
@item.CreateDate.ToShamsiWithTime()
</small>
</td>
<td class="text-center">
@if (item.IsActive)
{
<span class="badge bg-success">فعال</span>
}
else
{
<span class="badge bg-secondary">غیرفعال</span>
}
</td>
<td class="text-center">
@if (item.IsDeleted)
{
<span class="badge bg-danger">حذف شده</span>
}
else
{
<span class="badge bg-success">سالم</span>
}
</td>
<td class="text-center">
<div class="btn-group btn-group-sm">
<a class="btn btn-outline-primary"
asp-action="Edit"
asp-route-id="@item.UserId">
ویرایش
</a>
<a class="btn btn-outline-dark"
asp-action="Details"
asp-route-id="@item.UserId">
جزئیات
</a>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
<partial name="_AdminPagination" model="Model.GetCurrentPaging()" />
</div>
@section Scripts {
@if (TempData["SuccessMessage"] != null)
{
<script>
Swal.fire({
icon: 'success',
title: 'موفقیت',
text: '@TempData["SuccessMessage"]',
confirmButtonText: 'باشه'
});
</script>
}
<script>
document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll(".page-item a.page-link").forEach(function (el) {
el.addEventListener("click", function (e) {
e.preventDefault(); // جلوگیری از رفتن به href
const href = this.getAttribute("href");
const urlParams = new URLSearchParams(href.split("?")[1]);
const pageId = urlParams.get("PageId");
// مقدار PageId را در فرم قرار بده
document.querySelector("#filterForm input[name='PageId']").value = pageId;
// فرم را سابمیت کن
document.getElementById("filterForm").submit();
});
});
});
</script>
}
یک باگی که وجود داشت این بود که در صورتی که کاربر فیلتری ایجاد کرده باشد که در خروجی آن چند صفحه از کاربران برگردد در صورتی که کاربر بین صفحات کاربران فیلتر شده پیمایش کند مجددا فیلتر ها پاک میشوند که از کد جاوااسکریپی که در پایین کد ویو نوشته شده برای جلوگیری از پاک شدن فیلترها استفاده کرده ام (مجددا فرم را submitکرده ام).

یک تصمیم دیگه ای که داشتم این بود که برای فیلدهای تاریخ ایجاد کاربر (از تاریخ تا تاریخ) هم فیلتر بگذارم که البته فک کنم چون خیلی کاربردی نبود(به نظر من) دیگه نذاشتم.
یک کار دیگر هم که میشد انجام داد اینه که کاربر بتونه TakeEntity رو هم تغییر بده مثلا در هر صفحه 50 تا نمایش بده که اونم فقط کافیه یک فیلد به فرم اضافه کنیم که این مقدار رو از کاربر بگیره.
امیدوارم به کارتون اومده باشه
کدهای مربوط به ریپوزیتوری رو ننوشتم که دیگه همه داریمشون و مشخصه چیان .
سلام ، وقت بخیر
ممنون از شما