
🚀 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 NamespaceThe 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.
