Paginazione di un controllo Repeater tramite Stored Procedure

La paginazione consiste nel suddividere un elenco di dati su più pagine sfogliabili in modo da non appesantire troppo le pagine web e renderle più leggibili.

In alcuni controlli Asp.Net come il GridView la funzionalità di paginazione è integrata quindi facilmente applicabile agendo sulle proprietà del controllo stesso.
Il Repeater invece non offre questa possibilità, pertanto è necessario implementare la paginazione manualmente.

La necessità di usare un Repeater anziché un Grid nasce dal fatto di avere il totale controllo del codice Html generato. Questo è fondamentale in un sito pubblico seo-friendly. Inoltre il Grid è limitato a una visualizzazione di tipo tabellare.

Esistono diversi modi per paginare un Repeater. Uno di questi consiste nell'operare sul DataSet: in questo modo il data base restituisce tutte le righe al server web, poi ci pensa il codice Asp (o meglio VB) a selezionare le righe da visualizzare.

Per ottenere una paginazione più simile a quella che si effettua in PHP si può ricorrere a una Stored Procedure. Chiaramente questa è applicabile solo su data base Microsoft SQL Server. Questa tecnica permette di ridurre la quantità di informazioni che viene trasmessa dal server su cui risiede il data base a quello su cui risiede IIS. In molte realtà si tratta di due macchine distinte. Inoltre spostare il lavoro su una Stored Procedure in genere comporta un miglioramento delle prestazioni complessive.
D'altro canto però questo vantaggio si paga con un maggiore sforzo di calcolo da parte di SQL Server poiché non deve semplicemente estrarre dei dati ma anche effettuare calcoli e generare una tabella temporanea.

Questo esempio è una versione semplificata del codice realmente usato per la pagina Default.aspx del sito Dev-oClock.com.

Creare la Stored Procedure

Di seguito lo script per la creazione della Stored Procedure.
In locale si può lanciare da SQL Server Management Studio, mentre ad esempio su Aruba si può eseguire dal pannello di  myLittleAdmin.

USE [dev-oclock-mssql]
GO
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO
-- =====================================================
-- Author:	Sergio Roberto Boarina
-- Create date: 03/07/2013
-- Description:	Paginazione articoli - Dev-oClock.com
-- =====================================================
CREATE PROCEDURE [dbo].[sp_GetArticlesHomePage]
	@current_page int = 1,
	@page_size int = 10
AS
BEGIN
	-- creo una tabella temporanea
	CREATE TABLE #temp_articles
	(
		ID int identity primary key,
		Title varchar(500),
		Preview text,
		Link varchar(500),
		Name varchar(256)
	)
	
	SET NOCOUNT OFF
	
	INSERT INTO #temp_articles(Title, Preview, Link, Name)
		SELECT Articoli.Title, Articoli.Preview, Articoli.Link, Categorie.Name
		FROM Categorie INNER JOIN Articoli ON Categorie.[ID] = Articoli.[Cat_ID]
		ORDER BY Articoli.Date_pub DESC, Articoli.ID DESC
		
	DECLARE @from int
	DECLARE @to int
	
	SET @from = @current_page * @page_size - @page_size
	SET @to = @current_page * @page_size
	
	-- estraggo dalla tabella temporanea solo i record della pagina corrente
	SELECT * FROM #temp_articles WHERE ID > @from AND ID <= @to
	
	-- restituisco anche il numero totale di record
	SELECT COUNT(*) FROM #temp_articles
	
	-- pulizia
	DROP TABLE #temp_articles
	
	SET NOCOUNT ON;
END
GO

Questa procedura accetta in ingresso due parametri: la pagina corrente e il numero di elementi che si vuole visualizzare per ogni pagina.

Poi si crea una tabella temporanea contenente tutti gli elementi, la quale comprende anche un campo di tipo contatore (ID).
Da questa tabella temporanea si estraggono solo i record che davvero si vogliono visualizzare nella pagina aspx.
Come informazione aggiuntiva si fornisce al chiamante anche il numero totale di record.

Dichiarare il Repeater

Questo è il codice della pagina Asp.Net contente il controllo asp:Repeater.

<asp:Repeater ID="rptArticles" runat="server">
	<ItemTemplate>
		<h2>
			<a href="<%# Eval("Link")%>"><%# Eval("Title")%></a></h2>
			<span>categoria:&nbsp;<%# Eval("Name")%></span>
			<div><%# Eval("Preview")%></div>
	</ItemTemplate>
</asp:Repeater>

<asp:Label ID="lblNavLink" runat="server"></asp:Label>

Richiamare la Stored Procedure e paginare il Repeater

Questo invece è il codice Vb.Net che nel mio caso si trova nella pagina di code behind chiamata Default.aspx.vb

Imports System.Data
Imports System.Data.SqlClient
Imports System.Configuration

...

Protected Sub Page_Load(sender As Object, e As System.EventArgs) Handles Me.Load
	Dim strConnectionString As String
	Dim objConnection As SqlConnection
	Dim objCommand As SqlCommand
	Dim objDataAdapter As SqlDataAdapter
	Dim objDataSet As DataSet

	Dim currentPage As String
	Dim pageSize As Integer = 10	' Numero di articoli per pagina
	Dim totElements As Integer	' Totale articoli
	Dim numPages As Integer	' Numero di pagine

	Dim NavLink As StringBuilder = New StringBuilder()

	'Leggo la pagina corrente dalla Querystring; per default è = 1
	currentPage = Request.QueryString.Get("page")
	If String.IsNullOrEmpty(currentPage) Then
		currentPage = 1
	End If

	'Leggo la stringa di connessione dal web.config e apro la connessione al Data Base
	strConnectionString = ConfigurationManager.ConnectionStrings("dev-oclock-mssql").ConnectionString
	Try
		objConnection = New SqlConnection(strConnectionString)
	Catch err As Exception
		'Label1.Text &= err.Message & " " & e.ToString
	End Try

	'Invoco la Stored Procedure e creo il Data Set
	objCommand = New SqlCommand("sp_GetArticlesHomePage", objConnection)
	objCommand.CommandType = CommandType.StoredProcedure
	objCommand.Parameters.Add("@current_page", SqlDbType.Int).Value = CType(currentPage, Integer)
	objCommand.Parameters.Add("@page_size", SqlDbType.Int).Value = pageSize

	objDataAdapter = New SqlDataAdapter(objCommand)
	objDataSet = New DataSet
	objDataAdapter.Fill(objDataSet)

	'Popolo il Repeater
	rptArticles.DataSource = objDataSet
	rptArticles.DataBind()

	totElements = objDataSet.Tables(1).Rows(0)(0)
	numPages = totElements / pageSize
	If (numPages * pageSize) < totElements Then
		numPages += 1
	End If

	'Genero i link di navigazione
	For i As Integer = 1 To numPages
		If i = currentPage Then
			'la pagina corrente non deve essere un link
			NavLink.AppendFormat(i)
		Else
			NavLink.AppendFormat("<a href=""Default.aspx?page={0}"">{0}</a> ", i)
		End If
	Next
	lblNavLink.Text = NavLink.ToString

	objConnection.Close()
End Sub

Si richiama la Stored Procedure dandole in pasto il numero di pagina che si vuole visualizzare e il numero di elementi per pagina (in questo caso 10).
Il numero di pagina viene letto dalla querystring, se non è presente viene impostato per default a 1.

Poi si crea il DataSet con i record e si popola il Repeater.

Conoscendo il numero di record totale e la dimensione di ogni pagina si ricava il numero di pagine totali. Quindi si generano i link per poter navigare tra le pagine; i link non fanno altro che richiamare la stessa pagina passando in querystring il numero della pagina da visualizzare.

Autore: Sergio Roberto Boarina