🚀 ASP.NET GridView Tutorial: CRUD Operations with Built-In Paging (C# & VB.NET)
SQL Server Table Setup
To begin, we'll extract data from a SQL Server database table named Books, which stores essential information such as book titles, categories, prices, and more. This table serves as the foundation for our data-driven application. You can view the table structure here.
Once the table is created, the next step is to build a Web API using ASP.NET. This API will include a controller to handle HTTP requests and a model to represent the data structure. Together, they form the backbone of a clean and scalable architecture for retrieving and displaying paginated book data using modern JavaScript with async and await.
The Web API
🔹 Model (Books.cs in C#)
Let's begin by creating a Model for our API that will have few properties in it.
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace BooksApp.Models { public class Books { public int BookId { get; set; } public string BookName { get; set; } public string Category { get; set; } public decimal Price { get; set; } public decimal Total { get; set; } } }
🔹 Model (Books.vb)
Imports System.Web Namespace BooksApp.Models Public Class Books Public Property BookID() As Integer Get Return m_BookID End Get Set(value As Integer) m_BookID = value End Set End Property Private m_BookID As Integer Public Property BookName() As String Get Return m_BookName End Get Set(value As String) m_BookName = value End Set End Property Private m_BookName As String Public Property Category() As String Get Return m_Category End Get Set(value As String) m_Category = value End Set End Property Private m_Category As String Public Property Price() As Decimal Get Return m_Price End Get Set(value As Decimal) m_Price = value End Set End Property Private m_Price As Decimal Public Property Total() As Decimal Get Return m_Total End Get Set(value As Decimal) m_Total = value End Set End Property Private m_Total As Decimal End Class End Namespace
🔹 The Controller "BooksController.cs" (C#)
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.http; using System.Web.http; using BooksApp.Models; using System.Data.SqlClient; namespace BooksApp.Controllers { public class BooksController : ApiController { List object will hold and return a list of books. List<Books> MyBooks = new List<Books>(); string sConnString = "Data Source=DEMO;Persist Security Info=False;" + "Initial Catalog=DNA_Classified;User Id=sa;Password=sss;Connect Timeout=30;"; public IEnumerable<Books> Get(int iPg) { getTheBooks(iPg); return MyBooks; } public void getTheBooks(int iPage) { int iPageSize = 5; // You can change the page size according to your requirement. decimal iTotalPage = 0; SqlConnection myConn = new SqlConnection(sConnString); // SQL query to fetch books. string sSql = + "SELECT *, (SELECT COUNT(*) FROM dbo.Books) TotalBooks " + "FROM (SELECT ROW_NUMBER() OVER ( ORDER BY BookID ) AS RowNum, * " + "FROM dbo.Books " + ") AS r " + "WHERE RowNum >= ((" + iPageSize + " * " + (iPage == 1 ? 0 : iPage - 1) + " ) + 1) " + "AND RowNum <= (" + iPageSize + " * " + iPage + ") " + "ORDER BY RowNum"; SqlCommand objComm = new SqlCommand(sSql, myConn); myConn.Open(); SqlDataReader reader = objComm.ExecuteReader(); // Populate the list with data. while (reader.Read()) { iTotalPage = (int)reader["TotalBooks"]; iTotalPage = Math.Round(iTotalPage / iPageSize, 0); MyBooks.Add(new Books { BookId = (int)reader["BookID"], BookName = reader["BookName"].ToString(), Category = reader["Category"].ToString(), Price = Convert.ToDecimal(reader["Price"]), Total = iTotalPage }); } myConn.Close(); } } }
🔹 The Controller "BooksController.vb" (VB.NET)
Option Explicit On Imports System.Net.http Imports System.Web.http Imports System.Data.SqlClient Imports BooksApp.BooksApp.Models Namespace BooksApp Public Class BooksController Inherits ApiController List object will hold and return a list of books. Dim MyBooks As New List(Of Books)() Const sConnString As String = "Data Source=DEMO;Persist Security Info=False;" & _ "Initial Catalog=DNA_Classified;User Id=sa;Password=sss;Connect Timeout=30;" Public Function [Get](iPg As Integer) As IEnumerable(Of Books) getTheBooks(iPg) Return MyBooks End Function Public Sub etTheBooks(ByVal iPage As Integer) Dim iPageSize As Integer = 5 // You can change the page size according to your requirement. Dim iTotalPage As Integer = 0 Using con As SqlConnection = New SqlConnection(sConnString) // SQL query to fetch books. Dim sSQL = & _ "SELECT *, (SELECT COUNT(*) FROM dbo.Books) TotalBooks " & _ "FROM (SELECT ROW_NUMBER() OVER ( ORDER BY BookID ) AS RowNum, * " & _ "FROM dbo.Books " & _ ") AS r " & _ "WHERE RowNum >= ((" & iPageSize & " * " & IIf(iPage = 1, 0, iPage - 1) & " ) + 1) " & _ "AND RowNum <= (" & iPageSize & " * " & iPage & ") " & _ "ORDER BY RowNum" Dim objComm As New SqlCommand(sSQL, con) con.Open() Dim reader As SqlDataReader = objComm.ExecuteReader() ' Populate the list with data. While reader.Read() iTotalPage = reader("TotalBooks") / iPageSize MyBooks.Add(New Books() With { _ .BookID = CInt(reader("BookID")), _ .BookName = reader("BookName").ToString(), _ .Category = reader("Category").ToString(), _ .Price = CDbl(reader("Price")), _ .Total = iTotalPage }) End While con.Close() End Using End Sub End Class End Namespace
The Markup: Structuring the Page for Dynamic Data Display
The page starts with a simple <div> container. Using JavaScript, I'll dynamically build an HTML <table> with headers, rows, and pagination, all styled with custom CSS classes. These styles not only enhance the look but also drive the paging logic for a smooth user experience.
<!DOCTYPE html> <html lang="en"> <head> <title>Display Paginated Data from SQL Server using Web API and Async/Await</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } #showData { max-width: 600px; margin: auto; } table { width: 100%; border-collapse: collapse; margin-bottom: 10px; } th, td { border: 1px solid #eee; padding: 8px; text-align: center; } th { background-color: #f5f5f5; } .pageCountActive, .pageCountInactive { width: 20px; float: left; border: solid 1px #EEE; text-align: center; padding: 2px 3px; margin: 1px; cursor: pointer; color: #1464F4; } .pageCountInactive { color:#999; cursor:default; } .pageCountActive a { text-decoration:none; } </style> </head> <body> <main> <section id="showData" aria-label="Book Data Table"></section> </main> </body>
The Script
<script> function BindControls() { var page = 1; getData(page); async function getData(pg) { try { const response = await fetch(`/api/books/${pg}`); if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); const data = await response.json(); let myBooks = []; let col = []; data.forEach((book, i) => { myBooks.push(book); }); // Create table const table = document.createElement('table'); // Get headers myBooks.forEach(book => { for (let key in book) { if (!col.includes(key)) { col.push(key); } } }); // Create header row let tr = table.insertRow(-1); col.forEach(header => { const th = document.createElement("th"); th.innerHTML = header; tr.appendChild(th); }); // Add data rows myBooks.forEach(book => { tr = table.insertRow(-1); col.forEach(header => { const tabCell = tr.insertCell(-1); tabCell.innerHTML = book[header]; }); }); // Display the table const divContainer = document.getElementById('showData'); divContainer.innerHTML = ""; divContainer.appendChild(table); // Create pagination const divPager = document.createElement('div'); for (let i = 1; i <= myBooks[0].Total; i++) { const divPageCount = document.createElement('div'); divPageCount.innerHTML = pg === i ? `<span class="pageCountInactive">${i}</span>` : `<span class="pageCountActive" id="${i}">${i}</span>`; divPager.append(divPageCount); } divContainer.append(divPager); } catch (error) { alert(`Error: ${error.message}`); } } // Enable pagination by listening for clicks on active page numbers. document.addEventListener('click', function (event) { const target = event.target; if (target.classList.contains('pageCountActive')) { const page = parseInt(target.id, 10); if (!isNaN(page)) { getData(page); } } }); } BindControls(); </script>
That's it.
Why async/await Is Better Than jQuery Ajax
As mentioned earlier, my previous example used jQuery Ajax to call the Web API and retrieve data. While it achieved the intended result, this updated approach offers a cleaner and more modern alternative. Here's why:
The code is more readable and cleaner
1. async/await makes asynchronous code look and behave like synchronous code.
2. You avoid deeply nested callbacks, which are common in jQuery Ajax and lead to "callback hell".
Simplified Error Handling
1. With jQuery Ajax, error handling is done via separate error callbacks.
2. With async/await, you can use try...catch blocks, making it easier to manage and debug errors.
No External Dependency
1. async/await uses native JavaScript features—no need to load jQuery or rely on its syntax.
2. This reduces page weight and improves performance.