Relational Artist Cookbook: Web Classes

We will now use the data classes created for the cookbook to create a web application that pages through recordsets.

The IPageable interface and the PagedGrid web control

In Org.RelationalArtist.WebControls you will find a class PagedGrid. This class inherits from the standard DataGrid control, and operates on collections that implement the IPageable interface. In order to be IPageable, a collection must first be a valid IEnumerable, as such that the foreach construct can operate on it, and it must also implement the following methods and properties:

		// Number of records and pages
		int GetTotalNumberOfRecords();
		int GetTotalNumberOfPages(int pTotalNumberOfRecords); 
		int GetTotalNumberOfPages();
		
		// Can browse forward/backward
		bool HasNextPage {get;}
		bool HasPreviousPage { get; }
		
		//Maximum records (in a page) and Count of effective number of records in the page
		int MaxRecords { get; }
		int Count{ get; }

The PagedGrid web control relieves the webform that uses it, from part of the implementation burden.

The Suppliers by Id web form

The web form SuppliersSortedById simply pages through the supplier table, sorted by supplier id.

The web form

<%@ Register TagPrefix="CookBook" TagName="Menu" Src="Menu.ascx" %>
<%@ Register TagPrefix="RA" Namespace="Org.RelationalArtist.WebControls"
	Assembly="Org.RelationalArtist.WebControls"  %>
<%@ Import Namespace="Org.RelationalArtist.CookBook" %>
<%@ Page language="c#" Codebehind="SuppliersSortedById.aspx.cs" AutoEventWireup="false" 
		Inherits="Org.RelationalArtist.CookBook.Web.SuppliersSortedById" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
	<body>
		<COOKBOOK:MENU id="Menu" NAME="Menu" runat="server"
			></COOKBOOK:MENU>
		<h2><asp:label id="mLabelTitle" Runat="server"></asp:label></h2>
		<form id="SuppliersSortedById" method="post" runat="server">
			<RA:PAGEDGRID id="SupplierGrid" OnClickPrevious="PagePrevious" 
				OnClickNext="PageNext" runat="server" EnableViewState="False"
				Width="543px" Font-Names="Courier New" 
				AllowPaging="True" AutoGenerateColumns="False" CellPadding="4"
				BackColor="White" BorderWidth="1px" BorderStyle="None" 
				BorderColor="#3366CC" Height="272px" 
				HeaderStyle-BackColor="Black" HeaderStyle-Font-ForeColor="White" 
				HeaderStyle-Font-Bold="True" AlternatingItemStyle="Wheat" 
				ItemStyle-BackColor="White">
				<SelectedItemStyle Font-Bold="True" ForeColor="#CCFF99" 
					BackColor="#009999"></SelectedItemStyle>
				<ItemStyle ForeColor="#003399" BackColor="White"></ItemStyle>
				<HeaderStyle Font-Bold="True" ForeColor="#CCCCFF" 
					BackColor="#003399"></HeaderStyle>
				<FooterStyle ForeColor="#003399" BackColor="#99CCCC"></FooterStyle>
				<Columns>
					<ra:TemplateColumn HeaderText="Id">
						<ItemTemplate>
							<%# ((EntSupplier)Container.DataItem).SuppId %>
						</ItemTemplate>
					</ra:TemplateColumn>
					<ra:TemplateColumn HeaderText="Name">
						<ItemTemplate>
							<%# ((EntSupplier)Container.DataItem).SuppName %>
						</ItemTemplate>
					</ra:TemplateColumn>
					<ra:TemplateColumn HeaderText="Address">
						<ItemTemplate>
							<%# ((EntSupplier)Container.DataItem).SuppAddress %>
						</ItemTemplate>
					</ra:TemplateColumn>
				</Columns>
			</RA:PAGEDGRID>
		</form>
	</body>
</HTML>

In a first directive, it declares the Menu user control:

<%@ Register TagPrefix="CookBook" TagName="Menu" Src="Menu.ascx" %>

In a second directive, it declares the Org.RelationalArtist.WebControls library in the 'RA' xml namespace:

<%@ Register TagPrefix="RA" Namespace="Org.RelationalArtist.WebControls" 
	Assembly="Org.RelationalArtist.WebControls"  %>

In a following directive, it ensures that the EntSupplier type, that will be used later on, is declared as well:

<%@ Import Namespace="Org.RelationalArtist.CookBook" %>

The last directive is added by VS.NET automatically, and associates the code-behind file with the web form:

<%@ Page language="c#" Codebehind="SuppliersSortedById.aspx.cs" AutoEventWireup="false"  
	Inherits="Org.RelationalArtist.CookBook.Web.SuppliersSortedById" %>

Most of the attributes in the RA:PagedGrid are added by VS.NET automatically, when you use the autoformat feature for DataGrid controls. The only part that must be added manually, are the attributes OnClickPrevious and OnClickNext. The attributes OnClickPrevious="PagePrevious" OnClickNext="PageNext" associate the events with the methods PagePrevious(...) and PageNext(...) in the code-behind file.

In the <Columns> section only TemplateColumns can be used. The Container.DataItem will be of the type that is contained in the IPageable associated. In this case, it will be EntSupplier objects. In order to show the SuppId column in the PagedGrid, we declare the following ItemTemplate:

((EntSupplier)Container.DataItem).SuppId

The expression narrows Container.DataItem to an EntSupplier. This cast ensures that the EntSupplier properties can be accessed.

The code-behind file

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Org.RelationalArtist.CookBook.Web
{
	/*-----------------------------------------------
			CLASS DEFINITION
	-----------------------------------------------*/
	/// <summary>
	/// This web page shows suppliers sorted by Id.
	/// </summary>
	public class SuppliersSortedById: System.Web.UI.Page
	{
		protected Org.RelationalArtist.WebControls.PagedGrid SupplierGrid;
		protected System.Web.UI.WebControls.Label mLabelTitle;
		/*-----------------------------------------------
			PAGE LOAD
		-----------------------------------------------*/	
		private void Page_Load(object sender, System.EventArgs e)
		{
			if(!this.IsPostBack)
			{
				PageFirst();
			} 
		}
		/*-----------------------------------------------
			PAGE FIRST
		-----------------------------------------------*/	
		protected void PageFirst()
		{
			//init title
			mLabelTitle.Text="Suppliers sorted by Id";
			//	
			//init page
			SupplierGrid.PagedCollection=new EntSupplierPage();
			((EntSupplierPage)SupplierGrid.PagedCollection).FindFirstPageNoConditions();
			SupplierGrid.InitControlsFirstPage();
			this.StoreKeys();
		}
		/*-----------------------------------------------
			PAGE NEXT
		-----------------------------------------------*/	
		protected void PageNext(object sender, EventArgs e)
		{
			//init page
			SupplierGrid.PagedCollection=new EntSupplierPage();
			((EntSupplierPage)SupplierGrid.PagedCollection).FindNextPageNoConditions(LastSupplierId);
			SupplierGrid.InitControlsNextPage();
			this.StoreKeys();
		}
		/*-----------------------------------------------
			PAGE PREVIOUS
		-----------------------------------------------*/	
		protected void PagePrevious(object sender, EventArgs e)
		{
			//init page
			SupplierGrid.PagedCollection=new EntSupplierPage();
			((EntSupplierPage)SupplierGrid.PagedCollection).FindPreviousPageNoConditions(
				FirstSupplierId);
			SupplierGrid.InitControlsPreviousPage();
			this.StoreKeys();
		}
		/*-----------------------------------------------
			STORE KEYS
		-----------------------------------------------*/	
		protected void StoreKeys()
		{
			//remember first & last page keys
			if(SupplierGrid.PagedCollection.Count>0)
			{
				this.FirstSupplierId=((EntSupplierPage)SupplierGrid.PagedCollection).First.SuppId;
				this.LastSupplierId=((EntSupplierPage)SupplierGrid.PagedCollection).Last.SuppId;
			}
			else
			{
				this.FirstSupplierId=0;
				this.LastSupplierId=0;
			}
		}

		/*-----------------------------------------------
			FIRST SUPPLIER ID
		-----------------------------------------------*/	
		protected int FirstSupplierId 
		{
			get { return (int) ViewState["FirstSupplierId"]; }
			set { ViewState["FirstSupplierId"]=value; }
		}
		/*-----------------------------------------------
			LAST SUPPLIER ID
		-----------------------------------------------*/	
		protected int LastSupplierId 
		{
			get { return (int) ViewState["LastSupplierId"]; }
			set { ViewState["LastSupplierId"]=value; }
		}

		#region Web Form Designer generated code
		override protected void OnInit(EventArgs e)
		{
			//
			// CODEGEN: This call is required by the ASP.NET Web Form Designer.
			//
			InitializeComponent();
			base.OnInit(e);
		}
		
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{    
			this.ID = "SuppliersSortedById";
			this.Load += new System.EventHandler(this.Page_Load);

		}
		#endregion
	}
}

The class-behind will be entered in three possible ways.

When the page is entered for the first time, the Page_Load method will be triggered. Since it is not a PostBack, The method PageFirst() will be invoked. The PageFirst() method first initializes the title:

			mLabelTitle.Text="Suppliers sorted by Id";

Since mLabelTitle has its view state enabled, the label will maintain its value in subsequent invokations of the page.

Next, we initialize the PagedGrid, named SupplierGrid, with a PagedCollection. We create a new EntSupplierPage, and we initialize it by invoking its FindFirstPageNoConditions() initializer; which fills the collection with its first series of records. Now, we initialize the controls in the SupplierGrid by invoking its InitControlsFirstPage() method. Finally, we make sure to store the keys of the first and the last record in the PagedCollection by invoking the StoreKeys() method:

			SupplierGrid.PagedCollection=new EntSupplierPage();
			((EntSupplierPage)SupplierGrid.PagedCollection).FindFirstPageNoConditions();
			SupplierGrid.InitControlsFirstPage();
			this.StoreKeys();

The two other entry points are the methods PagePrevious() and PageNext(). They are very similar to PageFirst() and handle the postback events. In case of postback, the Page_Load() will not do anything.

First and last keys of the paged collection are stored in the viewstate; which ensures that they will be returned by the browser and be available for subsequent pages:

		protected int LastSupplierId 
		{
			get { return (int) ViewState["LastSupplierId"]; }
			set { ViewState["LastSupplierId"]=value; }
		}

The Suppliers by Name and Products By Name for Supplier web forms

Both forms work similarly to the SuppliersById web form. You will find both web forms in the CookBook sources:


The suppliers sorted by name web form creates a link for each supplier to the products they can supply: