Để hiển thị dữ liệu có phân trang trên DataGridView trong ứng dụng Windows Forms kết nối qua WebService, cần thiết lập cơ chế truyền – nhận dữ liệu dạng tuần tự hóa (serialization), kiểm soát luồng yêu cầu và xử lý logic phân trang cả ở phía client lẫn server.
1. Thành phần phía client
Client khởi tạo một đối tượng PagedResult<Product> để chứa kết quả phân trang (bao gồm danh sách bản ghi hiện tại, tổng số trang, chỉ số trang hiện hành…). Hàm truy vấn thực hiện các bước sau:
- Gọi hàm xác thực SOAP header trước khi gửi yêu cầu đến WebService.
- Lấy điều kiện tìm kiếm từ điều khiển nhập liệu (ví dụ:
txtSearchTerm.Text). - Gửi yêu cầu tới phương thức
FetchProductsByCriteriavới các tham số: chuỗi tìm kiếm, trạng thái sản phẩm, ID người dùng, số trang hiện tại (currentPage) và kích thước trang (itemsPerPage). - Nhận chuỗi XML đã được mã hóa URL làm đầu ra, sau đó giải mã và khôi phục thành đối tượng
PagedResult<Product>bằng tiện ích mở rộngXmlDeserializer. - Thiết lập
AutoGenerateColumns = falseđể kiểm soát cột hiển thị, gán nguồn dữ liệu trực tiếp từ thuộc tínhData, đồng thời cập nhật nhãn hiển thị số trang.
private PagedResult<Product> currentResult;
private bool LoadProducts(string searchTerm, ProductStatus status, int currentPage)
{
AuthenticateSoapHeader(); // Xác thực qua SOAP header
string encodedQuery = Uri.EscapeDataString(searchTerm ?? "");
string response = productService.FetchProductsByCriteria(
encodedQuery,
status,
currentUser.Id,
currentPage,
ItemsPerPage
);
if (string.IsNullOrEmpty(response))
return false;
currentResult = XmlDeserializer.FromXml<PagedResult<Product>>(
Uri.UnescapeDataString(response)
);
dataGridView1.AutoGenerateColumns = false;
dataGridView1.DataSource = currentResult.Data;
dataGridView1.ClearSelection();
lblTotalPages.Text = currentResult.TotalPages.ToString();
lblCurrentPage.Text = currentResult.CurrentPage.ToString();
return true;
}
2. Thành phần phía WebService
Phương thức WebMethod được đánh dấu với [SoapHeader("authHeader")] và [WebMethod(EnableSession = true)]. Sau khi xác thực thành công, hệ thống xây dựng truy vấn LINQ dựa trên điều kiện tìm kiếm và trạng thái sản phẩm, sau đó áp dụng phân trang bằng lớp PagedResult<T>:
[SoapHeader("authHeader")]
[WebMethod(EnableSession = true)]
public string FetchProductsByCriteria(string encodedSearchTerm, ProductStatus status, string userId, int page, int size)
{
if (!authHeader.IsValid())
return Uri.EscapeDataString(new PagedResult<Product>(null, page, size).ToXml());
string searchTerm = Uri.UnescapeDataString(encodedSearchTerm);
using var repo = new DataRepository();
IQueryable<Product> baseQuery = from p in repo.Products
join d in repo.ProductDetails on p.Id equals d.ProductId
where (p.OwnerId == userId || d.SellerId == userId) && p.Status == status
select p;
if (!string.IsNullOrWhiteSpace(searchTerm))
{
baseQuery = baseQuery.Where(p =>
p.Name.Contains(searchTerm) ||
p.Code.StartsWith(searchTerm) ||
p.Description.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase) >= 0
);
}
var result = new PagedResult<Product>(baseQuery, page, size);
return Uri.EscapeDataString(result.ToXml());
}
3. Lớp hỗ trợ phân trang PagedResult<T>
Lớp này đóng vai trò trung tâm trong việc đóng gói kết quả phân trang. Constructor thực hiện tính toán tổng số trang, đảm bảo chỉ số trang nằm trong giới hạn hợp lệ, và sử dụng Skip() + Take() để lấy tập con tương ứng:
public class PagedResult<T>
{
public List<T> Data { get; set; }
public int CurrentPage { get; set; }
public int TotalPages { get; set; }
public int TotalCount { get; set; }
public int PageSize { get; set; }
public PagedResult(IQueryable<T> source, int pageIndex, int pageSize)
{
Data = new List<T>();
PageSize = pageSize;
CurrentPage = Math.Max(1, Math.Min(pageIndex, GetMaxPage(source, pageSize)));
if (source?.Any() == true)
{
TotalCount = source.Count();
TotalPages = (int)Math.Ceiling(TotalCount / (double)pageSize);
Data = source
.Skip((CurrentPage - 1) * pageSize)
.Take(pageSize)
.ToList();
}
}
private static int GetMaxPage(IQueryable<T> source, int pageSize) =>
source == null ? 1 : (int)Math.Ceiling(source.Count() / (double)pageSize);
public string ToXml() => this.SerializeToXml();
}