posted on:4/3/2008

Introduction

Tired of continuously clicking next page, next page, next page to find the data you are looking for? I always thought this was just something you had to deal with when viewing data on the Internet, that was until I took a look at the new Microsoft live image search. Instead of forcing the user to click next page to paginate the data the page catches the scroll event and fetches the next page of data and adds it to the output asynchronously. In this project I will expand upon my previous project Using Linq to paginate your ObjectDataSource, replacing the standard grid with a scrolling paginated grid. This example works by first creating an Ext JS grid, making a call to a custom json rpc handler to retrieve data, and by adding an event listener to the scrolling event of our grid. This article will focus primarily on the AJAX portion of this project to read more on the underlying data pagination technique please take a look at the previous version of this project.

Topics covered in this project:

  • Singleton Collections
  • Querying objects using Linq
  • Using Skip() and Take() with Linq
  • Custom Data Pagination
  • Creating a custom Json RPC handler using Jayrock
  • Using the Ext JS with asp.net and Json RPC
  • Working with javascript events and asynchronous javascript

Background

This project is an example of using Linq to query a Singleton collection. This basically states that the collection persists in memory at the application level of your IIS process. This is achieved by using static or shared instances, or by using .net web.caching, or other third party cache mechanisms such as zcache or ncache. If you do not want to persist your data in memory then rownumber is another way for you to achieve data pagination. In place of the class that the Json RPC handler accesses to retrieve its data, you can replace the reference with a database query or object which loads directly from a database.

Using the code

The download zip file contains the Visual Studio 2008 project and source files necessary to run the online demo as well as a csv containing a zip code database and a stored procedure used to load the data and referenced inside the code. You will need to update the connection string located in the web.config file to point to your data source after you have imported the zip code data and ran the stored procedure script. In addition this zip contains the Ext js library and Jayrock dll's required to run this project.

Points of Interest

Before you get started you will need a mechanism for retrieving the data asynchronously as your user scrolls through the list of data. I choose to use a generic handler which returns a json serialized string for this task to maximize performance and throughput.

c#
using System;
using System.Web;
using Jayrock.Json;
using Jayrock.JsonRpc;
using Jayrock.JsonRpc.Web;
using ZipCodeObjects;
using System.Collections.Generic;
/// <summary> 
/// GetZips 
/// </summary> 
/// <remarks> 
/// Json Handler for zip code information. 
/// </remarks> 
public class GetZipCodes : JsonRpcHandler
{
    /// <summary> 
    /// GetZipCodesCount 
    /// </summary> 
    /// <returns></returns> 
    /// <remarks> 
    /// get count of all zip codes. 
    /// </remarks> 
    [JsonRpcMethod("GetZipCodesCount")]
    public int GetZipCodesCount()
    {
        return ZipCodeCollection.SelectCount();
    }
    /// <summary> 
    /// GetZipCodes 
    /// </summary> 
    /// <param name="ResultsPerPage"></param> 
    /// <param name="PageNumber"></param> 
    /// <returns></returns> 
    /// <remarks> 
    /// Get paginated zip codes data. 
    /// </remarks> 
    [JsonRpcMethod("GetZipCodesList")]
    public IEnumerable<ZipCode> GetZipCodesList(int ResultsPerPage, int PageNumber)
    {
        return ZipCodeCollection.GetZipCodes(ResultsPerPage, PageNumber);
    }
}

Vb.net
Imports System
Imports System.Web
Imports Jayrock.Json
Imports Jayrock.JsonRpc
Imports Jayrock.JsonRpc.Web
Imports LinqListGridBind.ZipCodeObjects
''' <summary>
''' GetZips
''' </summary>
''' <remarks>
''' Json Handler for zip code information.
''' </remarks>
Public Class GetZipCodes
    Inherits JsonRpcHandler
    ''' <summary>
    ''' GetZipCodesCount
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks>
    ''' get count of all zip codes.
    ''' </remarks>
    <JsonRpcMethod("GetZipCodesCount")> _
    Public Function GetZipCodesCount() As Integer
        Return ZipCodeCollection.SelectCount()
    End Function
    ''' <summary>
    ''' GetZipCodes
    ''' </summary>
    ''' <param name="ResultsPerPage"></param>
    ''' <param name="PageNumber"></param>
    ''' <returns></returns>
    ''' <remarks>
    ''' Get paginated zip codes data.
    ''' </remarks>
    <JsonRpcMethod("GetZipCodesList")> _
    Public Function GetZipCodesList(ByVal ResultsPerPage As Integer, _
                                       ByVal PageNumber As Integer) As IEnumerable(Of ZipCode)
        Return ZipCodeCollection.GetZipCodes(ResultsPerPage, PageNumber)
    End Function
End Class


This example uses no postbacks, viewstate or session state. Data is stored in the DOM and accessed via javascript.

//create ext data store and grid.
var store = null;
var grid = null;
//set default scroll variables.
var currentpos = 0;
var currentpage = 1;
//set constants.
//itemsperpage: number of records that appear on each page of content.
var itemsperpage = 14;
//number of pages of content to fetch when offset reached.
var pagestofetch = 2;
//number of pixel interval to fire handler request.
var scrolloffset = 263;
var scrolloffsetinterval = 263;
    
//event fires when ext is loaded and ready to display controls.
Ext.onReady(function(){
    
    //set current count.
    var x = document.getElementById("currentcount");
    x.innerHTML = (itemsperpage*pagestofetch);
    
    //create reference to GetZipCodes.ashx
    var s = new GetZipCodes();
    
    //call GetZipCodes.ashx handler
    //to retrieve total records in collection.
    s.GetZipCodesCount(function(response) { 
        var x = document.getElementById("totalcount");
        x.innerHTML = response.result;
    });
    
    //set skip take values.
    var y = document.getElementById("skip1");
        y.innerHTML = '0';
    var z = document.getElementById("skip2");
        z.innerHTML = '0';
    var a = document.getElementById("take1");
        a.innerHTML = (itemsperpage * pagestofetch);
    var b = document.getElementById("take2");
        b.innerHTML = (itemsperpage * pagestofetch);
        
    //set ext state provider.
    Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
    
    //initialize temp data store for handler response.
    var myData
    
    //call GetZipCodes.ashx to retrieve zip code data.
    s.GetZipCodesList(
    itemsperpage*pagestofetch,
    currentpage,
    function(response) { //async callback fires when handler returns data.
        //store response.
        myData = response;
        
        //create jason data store and load data.
        store = new Ext.data.JsonStore({data: myData,
                                        root: 'result',
                                        fields: ['zip', 
                                                 'city', 
                                                 'state', 
                                                 'latitude', 
                                                 'longitude', 
                                                 'timeZone', 
                                                 'dst']
                                        });
              
          
            //create the ext Grid and load data store
            grid = new Ext.grid.GridPanel({
                store: store,
                columns: [
                    {header: "Zip", width: 120, sortable: false, dataIndex: 'zip'},
                    {header: "City", width: 120, sortable: false, dataIndex: 'city'},
                    {header: "State", width: 120, sortable: false, dataIndex: 'state'},
                    {header: "Latitude", width: 120, sortable: false, dataIndex: 'latitude'},
                    {header: "Longitude", width: 120, sortable: false, dataIndex: 'longitude'},
                    {header: "Time Zone", width: 120, sortable: false, dataIndex: 'timeZone'},
                    {header: "DST", width: 120, sortable: false, dataIndex: 'dst'}
                ],
                stripeRows: true,
                height:350,
                autoExpandColumn:6,
                header: false,
                title:'Zip Code Listing'
            });
            //render grid
            grid.render('zip-grid');
            //add scroll event listener
            grid.addListener('bodyscroll',scrollListener);
        }); //end Json call listener.
          
   });  //end ext ready listener
       
//scrollListener
//fires when grid is scrolled.
function scrollListener(scrollLeft, scrollTop){
    //only handle scroll downs past highest position.
    if ( scrollTop > currentpos )
    {
        //check if we should get more data
        if ( scrollTop >  scrolloffset )
        {
            //store current grid scroll state.
            var state = grid.getView().getScrollState();
            
            //adjust scroll offset
            scrolloffset=scrollTop+scrolloffsetinterval;
            //adjust current page
            currentpage=currentpage+1;
            
            //initialize temp data store for handler response.
            var myData
            //call GetZipCodes.ashx to retrieve zip code data for next pages of data
            var s = new GetZipCodes();
            s.GetZipCodesList(
            itemsperpage*pagestofetch,
            currentpage,
            function(response) {  //async callback fires when handler returns data.
                //store response.
                myData = response;
                //append items to json store.
                store.loadData(myData,true);
                    
                //restore grid scroll state.
                grid.getView().restoreScroll(state);
                
                //set current count.
                var x = document.getElementById("currentcount");
                x.innerHTML = store.getCount();
                
                //set skip take values.
                var y = document.getElementById("skip1");
                    y.innerHTML = (((currentpage-1) * pagestofetch) * itemsperpage);
                var z = document.getElementById("skip2");
                    z.innerHTML = (((currentpage-1) * pagestofetch) * itemsperpage);
                var a = document.getElementById("take1");
                    a.innerHTML = (itemsperpage * pagestofetch);
                var b = document.getElementById("take2");
                    b.innerHTML = (itemsperpage * pagestofetch);
                
                 }); //end: async callback fires when handler returns data.
                 
        }  //end: check if we should get more data
        
        //reset current scroll position
        currentpos=scrollTop;
        
    } //end last position test.
    
} //end: scrollListener

Download the demo project for the complete source code and database. To view Linq and base class code online please take a look at Using Linq to paginate your ObjectDataSource.




posted on:3/19/2008
rownum

Introduction

When SQL server 2005 first launched I wrote about using ROW_NUMBER() to paginate your data with SQL Server 2005. Today I am going to look at an alternate approach on the same subject. In this article I will review data pagination using a grid view bound to an ObjectDataSource that is persistent in memory.

Topics Covered in this project:

  • Binding a GridView control to an ObjectDataSource.
  • Singleton Collections.
  • Using a GridView inside an Asp.net AJAX UpdatePanel.
  • Querying objects using Linq.
  • Using Skip() and Take() with Linq.
  • Custom Data Pagination.

Data Pagination

If you are unfamiliar with data pagination what you need to first need to understand is that out of the box you asp.net controls will paginate using UI pagination. Simply stated this means that all of the data is returned to your control and then filtered before the aspx renders the html to the client browser. This may be fine in some instances but generally this is bad for performance. Using a data pagination technique you will only return the data which will be displayed by your control. If you have already taken a look at the online demo of this project you already can see how much better the performance of a data pagination technique is in comparison.

Background

This project is an example of using Linq to query a Singleton collection. This basically states that the collection persists in memory at the application level of your IIS process. This is achieved by using static or shared instances, or by using .net web.caching, or other third party cache mechanisms such as zcache or ncache. If you do not want to persist your data in memory then rownumber is the best way for you to achieve data pagination.

Using the code

The download zip file contains the Visual Studio 2008 project and source files necessary to run the online demo as well as a csv containing a zip code database and a stored procedure used to load the data and referenced inside the code. You will need to update the connection string located in the web.config file to point to your data source after you have imported the zip code data and ran the stored procedure script.

Points of Interest

Using Linq syntax to retrieve "sub-collections" from your collections is a great alternative to a classic approach. Using a classic approach you would most times need to create a second instance of an object, loop through the first object and copy each of its members into the new new object. In this project I simply use the methods below to return a sub collection:

c#
        
/// <summary /> 
/// GetZipCodes 
/// </summary /> 
/// <param name="ResultsPerPage" /></param /> 
/// <param name="PageNumber" /></param /> 
/// <returns /> 
/// IEnumerable(Of ZipCode) 
/// </returns /> 
/// <remarks /> 
/// Page Clone of Instance Data Using Linq 
/// </remarks /> 
public static IEnumerable<ZipCodeObjects.ZipCode> GetZipCodes(int ResultsPerPage, int PageNumber)
{

    //number of records to skip 
    int skip = (PageNumber - 1) * ResultsPerPage;

    //number of results per page. 
    int take = ResultsPerPage;

    //execute Linq query for result set
    IEnumerable result = 
	(from zipcodes in ZipCodeInstance select zipcodes).Skip(skip).Take(ResultsPerPage);

    //return result 
    return result;
}

Vb.net
''' <summary>
''' GetZipCodes
''' </summary>
''' <param name="ResultsPerPage"></param>
''' <param name="PageNumber"></param>
''' <returns>
''' IEnumerable(Of ZipCode)
''' </returns>
''' <remarks>
''' Page Clone of Instance Data Using Linq
''' </remarks>
Public Shared Function GetZipCodes(ByVal ResultsPerPage As Integer, _
				   ByVal PageNumber As Integer) As IEnumerable(Of ZipCode)

    'number of records to skip
    Dim skip As Integer = (PageNumber - 1) * ResultsPerPage

    'number of results per page.
    Dim take As Integer = ResultsPerPage

    'execute query for result set
    Dim result As IEnumerable(Of ZipCode) = _
	(From zipcodes In ZipCodeInstance).Skip(skip).Take(take)

    'return result
    Return result
End Function

Here is the complete class, the contructor is set to private in the collection to ensure only one single instance of the collection. Additionally the lock or sync lock is used to ensure thread safety.

The in memory instance of your collection is not paged or altered, altering this instance would alter the instance for all users. For this reason we return smaller copies of the object when requested by the uer.

c#
using System;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using System.Linq;
using System.Collections.Generic;

namespace ZipCodeObjects
{
    /// <summary> 
    /// ZipCodeCollection 
    /// </summary> 
    /// <remarks> 
    /// Contains Collection of zip codes and meta. 
    /// </remarks> 
    public class ZipCodeCollection
    {

        /// <summary> 
        /// ReadLock 
        /// </summary> 
        /// <remarks> 
        /// Used By SyncLock to ensure Thread Safety 
        /// </remarks> 
        private static readonly object ReadLock = new object();

        /// <summary> 
        /// ZipCodeInstance 
        /// </summary> 
        /// <remarks> 
        /// ZipCodeInstance 
        /// Singleton Collection of Zip Codes and meta. 
        /// </remarks> 
        private static List<ZipCode> m_ZipCodeInstance;
        public static List<ZipCode> ZipCodeInstance
        {
            get
            {
                // initialize if not already done 
                if (m_ZipCodeInstance == null)
                {

                    //only allow 1 person to load data at once. 
                    lock (ReadLock)
                    {
                        if (m_ZipCodeInstance == null)
                        {
                            m_ZipCodeInstance = LoadData();
                        }
                    }

                }
                return m_ZipCodeInstance;
            }
        }

        /// <summary> 
        /// GetZipCodes 
        /// </summary> 
        /// <param name="ResultsPerPage"></param> 
        /// <param name="PageNumber"></param> 
        /// <returns> 
        /// IEnumerable(Of ZipCode) 
        /// </returns> 
        /// <remarks> 
        /// Page Clone of Instance Data Using Linq 
        /// </remarks> 
        public static IEnumerable<ZipCodeObjects.ZipCode> GetZipCodes(int ResultsPerPage, int PageNumber)
        {

            //number of records to skip 
            int skip = (PageNumber - 1) * ResultsPerPage;

            //number of results per page. 
            int take = ResultsPerPage;

            //execute Linq query for result set
            IEnumerable<ZipCodeObjects.ZipCode> 
		result = (from zipcodes in ZipCodeInstance select zipcodes).Skip(skip).Take(ResultsPerPage);

            //return result 
            return result;
        }

        /// <summary> 
        /// SelectCount 
        /// </summary> 
        /// <returns> 
        /// Integer 
        /// </returns> 
        /// <remarks> 
        /// Returns total number of records in instance. 
        /// Uses Linq 
        /// </remarks> 
        public static int SelectCount()
        {
            return (from zipcodes in ZipCodeInstance select zipcodes).Count();
        }

        /// <summary> 
        /// LoadData 
        /// </summary> 
        /// <returns> 
        /// List(Of ZipCode) 
        /// </returns> 
        /// <remarks> 
        /// Load collection of zip codes from database. 
        /// </remarks> 
        private static List<ZipCode> LoadData()
        {

            //create new instance of zip code collection 
            List<ZipCode> ziplist = new List<ZipCode>();

            //setup database connection 
            System.Data.SqlClient.SqlConnection conn = new System.Data.SqlClient.SqlConnection();
            conn.ConnectionString = ConfigurationManager.ConnectionStrings["ProgrammersJournal"].ConnectionString;

            //open connection 
            conn.Open();

            try
            {

                //define sql command 
                SqlCommand cmd = new SqlCommand("pj_getallzipcodes", conn);

                //execute and loop through reader. 
                using (System.Data.SqlClient.SqlDataReader reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {

                        //add zip object to list. 
                        ziplist.Add(new ZipCode(reader.GetSqlString(0).ToString(), 
						reader.GetSqlString(1).ToString(), 
						reader.GetSqlString(2).ToString(), 
						Convert.ToDecimal(reader.GetSqlString(3).ToString()), 
						Convert.ToDecimal(reader.GetSqlString(4).ToString()), 
						Convert.ToInt32(reader.GetSqlString(5).ToString()), 
						Convert.ToInt16(reader.GetSqlString(6).ToString())));
                    }
                }
            }

            catch (Exception ex)
            {

                //bubble exception 
                throw new Exception(ex.Message);
            }

            finally
            {

                //close connection 
                if (conn.State != ConnectionState.Closed)
                {
                    conn.Close();
                }

                //instance is populated 
                //force garbage collection 
                GC.Collect();
                GC.WaitForPendingFinalizers();

            }

            //return new instance of zip code collection 
            return ziplist;
        }

        /// <summary> 
        /// New 
        /// </summary> 
        /// <remarks> 
        /// Conrtructor set to private to ensure 
        /// instances of the obect cannot be created 
        /// outside of this class. 
        /// </remarks> 
        private ZipCodeCollection()
        {

        }

    } 

    /// <summary> 
    /// ZipCode 
    /// </summary> 
    /// <remarks> 
    /// Single Zip code record and associated meta. 
    /// </remarks> 
    public class ZipCode
    {

        /// <summary> 
        /// Zip 
        /// </summary> 
        /// <remarks> 
        /// Zip Code 
        /// </remarks> 
        private string m_Zip;
        public string Zip
        {
            get { return m_Zip; }
        }

        /// <summary> 
        /// City 
        /// </summary> 
        /// <remarks> 
        /// City where zip code is located. 
        /// </remarks> 
        private string m_City;
        public string City
        {
            get { return m_City; }
        }

        /// <summary> 
        /// State 
        /// </summary> 
        /// <remarks> 
        /// State where zip code is located. 
        /// </remarks> 
        private string m_State;
        public string State
        {
            get { return m_State; }
        }

        /// <summary> 
        /// Latitude 
        /// </summary> 
        /// <remarks> 
        /// Latitude reference for this zip code. 
        /// </remarks> 
        private decimal m_Latitude;
        public decimal Latitude
        {
            get { return m_Latitude; }
        }

        /// <summary> 
        /// Longitude 
        /// </summary> 
        /// <remarks> 
        /// Longitude reference for this zip code. 
        /// </remarks> 
        private decimal m_Longitude;
        public decimal Longitude
        {
            get { return m_Longitude; }
        }

        /// <summary> 
        /// TimeZone 
        /// </summary> 
        /// <remarks> 
        /// TimeZone reference for this zip code. 
        /// </remarks> 
        private int m_TimeZone;
        public int TimeZone
        {
            get { return m_TimeZone; }
        }

        /// <summary> 
        /// Dst 
        /// </summary> 
        /// <remarks> 
        /// Dst reference for this zip code. 
        /// </remarks> 
        private short m_Dst;
        public short Dst
        {
            get { return m_Dst; }
        }

        /// <summary> 
        /// New 
        /// </summary> 
        /// <remarks> 
        /// parameterless constructor 
        /// </remarks> 
        public ZipCode()
        {

        }

        /// <summary> 
        /// New 
        /// </summary> 
        /// <param name="zip"></param> 
        /// <param name="city"></param> 
        /// <param name="state"></param> 
        /// <param name="latitude"></param> 
        /// <param name="longitude"></param> 
        /// <param name="timeZone"></param> 
        /// <param name="dst"></param> 
        /// <remarks> 
        /// Custom Contructor 
        /// </remarks> 
        public ZipCode(string zip, string city, string state, decimal latitude, decimal longitude, int timeZone, short dst)
        {

            this.m_Zip = zip;
            this.m_City = city;
            this.m_State = state;
            this.m_Latitude = latitude;
            this.m_Longitude = longitude;
            this.m_TimeZone = timeZone;
            this.m_Dst = dst;

        }

    } 
}

Vb.net
Imports System.Data
Imports System.Data.SqlClient
Imports System.Configuration
Imports System.Linq

Namespace ZipCodeObjects

    ''' <summary>
    ''' ZipCodeCollection
    ''' </summary>
    ''' <remarks>
    ''' Contains Collection of zip codes and meta.
    ''' </remarks>
    Public Class ZipCodeCollection

        ''' <summary>
        ''' ReadLock
        ''' </summary>
        ''' <remarks>
        ''' Used By SyncLock to ensure Thread Safety
        ''' </remarks>
        Private Shared ReadOnly ReadLock As New Object()

        ''' <summary>
        ''' ZipCodeInstance
        ''' </summary>
        ''' <remarks>
        ''' ZipCodeInstance
        ''' Singleton Collection of Zip Codes and meta.
        ''' </remarks>
        Private Shared m_ZipCodeInstance As List(Of ZipCode)
        Public Shared ReadOnly Property ZipCodeInstance() As List(Of ZipCode)
            Get
                ' initialize if not already done
                If m_ZipCodeInstance Is Nothing Then

                    'only allow 1 person to load data at once.
                    SyncLock ReadLock
                        If m_ZipCodeInstance Is Nothing Then
                            m_ZipCodeInstance = LoadData()
                        End If
                    End SyncLock

                End If
                Return m_ZipCodeInstance
            End Get
        End Property

        ''' <summary>
        ''' GetZipCodes
        ''' </summary>
        ''' <param name="ResultsPerPage"></param>
        ''' <param name="PageNumber"></param>
        ''' <returns>
        ''' IEnumerable(Of ZipCode)
        ''' </returns>
        ''' <remarks>
        ''' Page Clone of Instance Data Using Linq
        ''' </remarks>
        Public Shared Function GetZipCodes(ByVal ResultsPerPage As Integer, _
                                           ByVal PageNumber As Integer) As IEnumerable(Of ZipCode)

            'number of records to skip
            Dim skip As Integer = (PageNumber - 1) * ResultsPerPage

            'number of results per page.
            Dim take As Integer = ResultsPerPage

            'execute query for result set
            Dim result As IEnumerable(Of ZipCode) = _
                (From zipcodes In ZipCodeInstance).Skip(skip).Take(take)

            'return result
            Return result
        End Function

        ''' <summary>
        ''' SelectCount
        ''' </summary>
        ''' <returns>
        ''' Integer
        ''' </returns>
        ''' <remarks>
        ''' Returns total number of records in instance.
        ''' Uses Linq
        ''' </remarks>
        Public Shared Function SelectCount() As Integer
            Return (From zipcodes In ZipCodeInstance).Count()
        End Function

        ''' <summary>
        ''' LoadData
        ''' </summary>
        ''' <returns>
        ''' List(Of ZipCode)
        ''' </returns>
        ''' <remarks>
        ''' Load collection of zip codes from database.
        ''' </remarks>
        Private Shared Function LoadData() As List(Of ZipCode)

            'create new instance of zip code collection
            Dim ziplist As New List(Of ZipCode)

            'setup database connection
            Dim conn As New System.Data.SqlClient.SqlConnection()
            conn.ConnectionString = ConfigurationManager.ConnectionStrings("ProgrammersJournal").ConnectionString

            'open connection
            conn.Open()

            Try

                'define sql command
                Dim cmd As New SqlCommand("pj_getallzipcodes", conn)

                'execute and loop through reader.
                Using reader As SqlClient.SqlDataReader = cmd.ExecuteReader()
                    While reader.Read()

                        'add zip object to list.
                        ziplist.Add(New ZipCode(reader.GetSqlString(0).ToString(), _
                                                reader.GetSqlString(1).ToString(), _
                                                reader.GetSqlString(2).ToString(), _
                                                Convert.ToDecimal(reader.GetSqlString(3).ToString()), _
                                                Convert.ToDecimal(reader.GetSqlString(4).ToString()), _
                                                Convert.ToInt32(reader.GetSqlString(5).ToString()), _
                                                Convert.ToInt16(reader.GetSqlString(6).ToString())))
                    End While
                End Using

            Catch ex As Exception

                'bubble exception
                Throw New Exception(ex.Message)

            Finally

                'close connection
                If conn.State <> ConnectionState.Closed Then
                    conn.Close()
                End If

                'instance is populated
                'force garbage collection
                GC.Collect()
                GC.WaitForPendingFinalizers()

            End Try

            'return new instance of zip code collection
            Return ziplist
        End Function

        ''' <summary>
        ''' New
        ''' </summary>
        ''' <remarks>
        ''' Conrtructor set to private to ensure 
        ''' instances of the obect cannot be created
        ''' outside of this class.
        ''' </remarks>
        Private Sub New()

        End Sub

    End Class

    ''' <summary>
    ''' ZipCode
    ''' </summary>
    ''' <remarks>
    ''' Single Zip code record and associated meta.
    ''' </remarks>
    Public Class ZipCode

        ''' <summary>
        ''' Zip
        ''' </summary>
        ''' <remarks>
        ''' Zip Code
        ''' </remarks>
        Private m_Zip As String
        Public ReadOnly Property Zip() As String
            Get
                Return m_Zip
            End Get
        End Property

        ''' <summary>
        ''' City
        ''' </summary>
        ''' <remarks>
        ''' City where zip code is located.
        ''' </remarks>
        Private m_City As String
        Public ReadOnly Property City() As String
            Get
                Return m_City
            End Get
        End Property

        ''' <summary>
        ''' State
        ''' </summary>
        ''' <remarks>
        ''' State where zip code is located.
        ''' </remarks>
        Private m_State As String
        Public ReadOnly Property State() As String
            Get
                Return m_State
            End Get
        End Property

        ''' <summary>
        ''' Latitude
        ''' </summary>
        ''' <remarks>
        ''' Latitude reference for this zip code.
        ''' </remarks>
        Private m_Latitude As Decimal
        Public ReadOnly Property Latitude() As Decimal
            Get
                Return m_Latitude
            End Get
        End Property

        ''' <summary>
        ''' Longitude
        ''' </summary>
        ''' <remarks>
        ''' Longitude reference for this zip code.
        ''' </remarks>
        Private m_Longitude As Decimal
        Public ReadOnly Property Longitude() As Decimal
            Get
                Return m_Longitude
            End Get
        End Property

        ''' <summary>
        ''' TimeZone
        ''' </summary>
        ''' <remarks>
        ''' TimeZone reference for this zip code.
        ''' </remarks>
        Private m_TimeZone As Integer
        Public ReadOnly Property TimeZone() As Integer
            Get
                Return m_TimeZone
            End Get
        End Property

        ''' <summary>
        ''' Dst
        ''' </summary>
        ''' <remarks>
        ''' Dst reference for this zip code.
        ''' </remarks>
        Private m_Dst As Short
        Public ReadOnly Property Dst() As Short
            Get
                Return m_Dst
            End Get
        End Property

        ''' <summary>
        ''' New
        ''' </summary>
        ''' <remarks>
        ''' parameterless constructor
        ''' </remarks>
        Public Sub New()

        End Sub

        ''' <summary>
        ''' New
        ''' </summary>
        ''' <param name="zip"></param>
        ''' <param name="city"></param>
        ''' <param name="state"></param>
        ''' <param name="latitude"></param>
        ''' <param name="longitude"></param>
        ''' <param name="timeZone"></param>
        ''' <param name="dst"></param>
        ''' <remarks>
        ''' Custom Contructor
        ''' </remarks>
        Public Sub New(ByVal zip As String, _
                       ByVal city As String, _
                       ByVal state As String, _
                       ByVal latitude As Decimal, _
                       ByVal longitude As Decimal, _
                       ByVal timeZone As Integer, _
                       ByVal dst As Short)

            Me.m_Zip = zip
            Me.m_City = city
            Me.m_State = state
            Me.m_Latitude = latitude
            Me.m_Longitude = longitude
            Me.m_TimeZone = timeZone
            Me.m_Dst = dst

        End Sub

    End Class

End Namespace

Binding your GridView to your collection is very easily done using an ObjectDataSource.

<asp:ObjectDataSource ID="ZipCodeObjectDataSource" runat="server" SelectMethod="GetZipCodes"
    TypeName="ZipCodeObjects.ZipCodeCollection">
    <SelectParameters>
    <asp:SessionParameter DefaultValue="20" Name="ResultsPerPage" SessionField="ResultsPerPage"
        Type="Int32" />
    <asp:SessionParameter DefaultValue="1" Name="PageNumber" SessionField="PageNumber"
        Type="Int32" />
    </SelectParameters>
</asp:ObjectDataSource>

From here all that is left is a simple paging mechanism and UI. Download the demo project for the complete source code and database.




posted on:3/18/2008 2:24:19 AM
highspeed

Part I: Overview and Architecture

Overview

In the hands of a master coder and architect Asp.net can be a very powerful platform for building high performance web applications. All too often Asp.net is used more like a rapid development tool rather then a framework for building high performance web applications. As a result the general view of the IT community is that general performance of .net web applications is inferior to that of it's competitors. This is unfortunate because though it is true that some platforms do offer slightly better overall performance, most of the performance issues in .net web applications are due to bad development practices.

In this article I will review the common mistakes made by some .net developers as well as document .net performance tips, suggestions and best practices for developers and architects when developing web applications in .net.

The Problem

Before we review guidelines and best practices we need to first address the problem. I find that many .net web developers do not consider the performance of their web applications until the point of near completion. At this point in the process their is very little that can be done to properly address the overall performance problems without rewriting the entire application.

The first thing that you need to do in order to write a high performance web application is admit to yourself that you are a developer working on a web site. Visual Studio has many nice features that work effectively to disguise your web application as a winforms application. These features can be great when implementing rapid development on a content management system which will have at most 10 simultaneous users sitting all of 10 feet away of the production environment, but if you are building a client facing web application this is frankly just not going to cut it. As a developer working on a high performance web application you will need to understand the execution of a .net web page and the architecture of a web development application. You will also need to write html, write javascript and understand object oriented concepts. This article will touch on many of these concepts but I urge you to be aware that ultimately your project is a web application which states that ultimately you will need to employ the skills of a web developer in order to achieve the results you require.

Be A Leader

As the developer of a high performance web application you will be required to make decisions in terms of business rules, best practices, architecture and implementation. If you want to build a high performance web application it is important that you be the champion of your web application. It will not fall into place by itself and no one is going to do it for you. Regardless of your role in your organization if you wish to build a high performance web application you will need to lead by practice and example.

Requirements Gathering

Despite your life-cycle development framework good up front requirements gathering is always required at the start of any high performance web application. The information gathered at this stage of development will define you business objects and business logic. These two items define your web application architecture and based on that architecture you will determine best practices for performance. It is very important that you as a developer come to an understanding and agreement with all parties of how everything will work. All to often I hear the excuse from a developer that the his perfect system was destroyed by the decisions made by others while he was in development. As a developer it is your responsibility to organize and manage your own project. You are the experienced professional and they are depending on you to make good choices. A good doctor does not treat his patients based solely on what they think their ailment is, he will first conduct tests to verify the ailment and consider other ailments before starting treatment.

Some advice on requirements gathering:
  • Meet strictly about business requirements: Often times organizations will try to kill many birds with one stone. You may find yourself being brought into a meeting which encapsulates marketing, design, goals and some business requirements. This is not enough information to begin working! Use this time to understand the basic concepts of your project and formulate the questions you need answered. Then schedule a second meeting with decision makers to define your business rules. Make sure you are prepared with the right questions for this meeting.
  • Make sure you understand the project completely: Before you put pen to paper make sure you understand everything completely. Don't be afraid to ask a question. In most cases if there is an item you do not understand it usually points to something that has not been thought through completely. You cannot design an application for peak performance without understanding it.
  • Trim the fat: Often times "the fat" is the cause of bad performance in web applications. Try to remove as many unnecessary business rules and features that you feel will effect performance. Make recommendations to the decision makers in your organization and provide alternate methods of achieving the same goals.
  • Define Risks Publicly: Don't keep risks to yourself, define them publicly. Any design pattern you choose will have a shortfall. Defining risks publicly will give players on your team a chance to chime in and make a correction before you are near completion of development.
  • Force people to review your specifications: After you have put together specifications make sure the decision makers in your organization read them and understand them. Often times they may tell you everything is good but they did not take the time to review your specifications completely.
  • Be adaptive and predictive: Despite all of the techniques defined above changes will creep their way into your web application project for the entirety of its lifetime. Understanding this, design your application to both adapt to changes and still deliver high performance.

Architecture

The .net framework does assume some generic architecture guidelines for your project but be sure that when defining your architecture that you are choosing the right architecture for your specific project. Be sure to research the latest and greatest technologies available to you at the time of implementation and analyze the performance advantages of each. As a general rule when designing the architecture of your project try to design it with a five year shelf life. This may seem like a short time but in the world of web development five years is a very long time. Also be sure to make your architecture scalable and adaptive while still retaining excellent performance.

Class Structure

One of the most common mistakes I see developers make is when they are designing applications is that they by default think about the data model first and class structure secondly. This often times results in a class structure that is simply a dump of the data model, or which is not object oriented at all. Using this methodology sells the potential performance of your web application short.

For this reason I recommend that you first design your class structure then design your database model. When considering performance at the class level many more options are available to you. You can define many complex geometric shapes such as cubes and trees and much data can be stored in a way that cannot be done as efficiently when forced to store data in flat relational tables.

  • Use Inheritance: Inheritance is a very basic concept that most developers have a good understand of but often times do not employ. In .net web application your web application will be compiled at runtime to the gac so unlike many interpreted web programming languages you will not get as much of a performance increase from using inheritance based on limiting the lines of code you write, but by defining relations between your objects using inheritance rather than creating new instances of objects to define relations the overall memory footprint of your web application will decrease as well as decreasing the amount of time it takes to construct an object.
  • Think about how your data objects persist in memory: From your requirements gathering you should have a good idea of how your business data objects should persist in memory. Generally in web applications your data will fall into 3 categories.
    • Data that is the same for every user and is frequently accessed: For data of this kind you will get best performance if all users access the same instance of the same data. this will limit the number of transactions back and fourth from our data store. Implementing these techniques will be covered more in debt in Part II of this article.
    • Data that is the same for each user and is frequently accessed: For data of this kind you will get best performance if you employ state management techniques to hold this data. Thus limiting transactions going back and fourth between your database and at the same time protected this data in an instance that is only accessible to the user. State management techniques will be covered more in debt in Part II of this article.
    • Real time data and data which is not frequently used: For this type of data it is best to just recall the data from your data store and will want to design your class structure in a way that it can be constructed an deconstructed efficiently. If your specific web application consists of mostly real time data you may want to consider implementing a SQL cache dependency for this data.
  • Think about how data will be accessed by your application: Also from your requirements gathering you should have a good understanding of how your objects and the data stored inside your objects will be accessed. You do not necessarily want to look at the best possible way of organizing your classes, rather you want to look at the best way of organizing your classes for your application. For instance, If your data were to be generally requested all at once it may be better stored in one method rather than another method which would be better if the data were to be selected one record at a time. Choosing the right data type is important for your implementation, carefully review you options and use the data type that best suits your needs.
  • Use smaller transactions when possible: At this point in the game if you find yourself designing large transactions you may want to reconsider your design pattern. For a high performance web application you should try to minimize the number of transactions it takes to accomplish any given task. For instance in an ecommerce system, if a user were to add an item to his or her cart, this should be one simple transaction. If you find yourself inserting the data into 3 different places and recalculating inventory, then you are doing something wrong.

Data Structure

Now after you have defined your class structure and have done good user requirements gathering you should have a much better idea of how your data model should look. There are already many articles online about data modeling so I am not going to go into too much in this article. For more generalized information on this topic please read Database Design and Modeling Fundamentals. Here are a few extra tips on maintaining good database performance.

  • For data that is frequently accessed try to use a model that is good for performance. You will find that by first defining your class model you will not need to define your business rules by the relationships of your tables. It is still a good practice to keep these types of dependencies in your database for integrity sake, but every single relation does not necessarily need to be reflected in your data if it will cause performance to fall off.
  • Avoid the need for aggregate functions: Like in designing your class, when designing your database model consider the way your data will be accessed, if at this stage you see that you will need to write aggregate functions for the basic functionality of your web application, you may want to reconsider your model. A key value pair system is nice, but if you need to return search results back on items containing the instances of a certain key value you are going to suffer performance problems.
  • Define Database Maintenance Procedures from the start: At this point of the process you will have a much better idea of what data can be safely removed or archived periodically. This is also the best time while decision makers are still engaged in the project to define these types of rules.

In the Building High Performance Web Applications in asp.net: Part II we will review actual implementation. Topics include: Caching, HTML, CSS, Javascript, State Management, Binding, Paging, Searching, Compression, Ajax, Asynchronous Programming and coding Tips and Techniques.



[ view comments: 11 ] [ comment ]