Click here to Skip to main content
14,969,910 members
Articles / Web Development / HTML
Article
Posted 25 Jan 2016

Stats

20.2K views
240 downloads
5 bookmarked

Using Multiple Asynchronous GridView Controls in ASP.NET

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
25 Jan 2016CPOL9 min read
Number of cases where GridView controls can be used effectively in ASP.NET web pages
In this article, there are scenarios in which we will see what could be the least complicated ways to use grid views and update them asynchronously from the client browser using JavaScript.

Introduction

The Grid View control is one of the most complicated and resource heavy components that we can have in our ASP.NET web pages. The Grid View control makes it very easy for us to create complex data grids with multiple columns and rows. There can be custom styling for the header, footer, data rows, alternate rows and so on. This makes it crucial for everyone who needs to use grid view control to have a good understanding of how this control should be used on both the server and client side.

While it remains true that the world is quickly moving on to pure JavaScript grid libraries and other such frameworks, there are still so many applications that are still dependent on ASP.NET grid views and a good number of devs who have to face the difficult challenge of maintaining those applications.

There can be many different scenarios in which we can use this control based on our requirements; often times, we need to have multiple grids on a single web page to show a variety of information to the user. These kinds of situations make it difficult to code the gridview properly. It becomes even more difficult when we need to update the grids asynchronously from JavaScript.

In this article, there are scenarios in which we will see what could be the least complicated ways to use grid views and update them asynchronously from the client browser using JavaScript.

Target Audience

This code should be beneficial to all those who have at some point worked with complex grid pages. Those who are already working on applications which extensively use multiple grid view controls to display loads of data can also use this article to properly manage the async updates to the grids.

Using the Code

This article will be addressing two cases where we can use single and multiple grid view controls. In the first scenario, there is a single grid which we need to update asynchronously.

The second case will be of multiple grids which we will need to update one by one, so in this case, we will need to do some kind of event chaining like we do when implementing JavaScript promises. This means that we can only make the async update of the second grid after the first grid's update has been finished.

Single Grid View Control

In this scenario, there is a grid view control inside an update panel. We will need the update panel to make async updates to the grid. The grid will be populated by test data generated by the code. If you want more robust testing, then you can wire up the data source of the grid with the actual database table.

There is an input which can be used to add more records to the grid. Whenever a new record is added, then we have to refresh the grid contents. For that purpose, we will need to fire the update event of the update panel enclosing the grid so that the server code used to bind the data to the grid can be executed.

This implementation is easy to understand. I have used __doPostBack to update the grid from the JavaScript code, this method serves little purpose to refresh a single grid, but because our next example has multiple grids, I am using __doPostBack so that you can understand how it works.

Let's walk through the code to see how things are working here. Add an empty ASP.NET web form to a new Web Application Project. Name the page SingleGrid.aspx and add an update panel named upMain. Inside the update panel, add a grid view control and name it gridMain.

Below the update panel, add a text box to input a new name to add to the data collection. Also add an HTML anchor whose click event will be wired to a JavaScript function to call the __doPostBack function.

We will need to wire up the events to add a new record to the data and then to update the grid control. Bind the document DOMContentLoaded event with the onLoad function and the anchor's click event with the AddRecord function. The AddRecord function will call the __doPostBack function to make an async update for the upMain update panel.

SingleGrid.aspx

ASP.NET
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="SingleGrid.aspx.cs" 
         Inherits="WebApplication1.SingleGrid" %>

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
    <form runat="server">
        <asp:ScriptManager ID="scriptManager1" runat="server" 
         AsyncPostBackTimeout="90"></asp:ScriptManager>

        <h3>All Records</h3>
        <asp:UpdatePanel ID="upMain" runat="server" UpdateMode="Conditional">
            <ContentTemplate>
                <asp:GridView ID="gridMain" runat="server" 
                 OnDataBinding="gridMain_DataBinding" CellPadding="10">
                </asp:GridView>
            </ContentTemplate>
        </asp:UpdatePanel>

        <h3>Add New Record</h3>
        <input id="txtName" type="text" placeholder="Name" />
        <br />
        <a href="#" id="lnkAddRecord">Add</a>

    </form>

    <script>
        var upMainID = '<%= upMain.ClientID%>';
        document.addEventListener("DOMContentLoaded", onLoad);
        var lnkAddRecord = null;

        function onLoad()
        {
            lnkAddRecord = document.querySelector('#lnkAddRecord');
            lnkAddRecord.addEventListener('click', AddRecord);
        }

        function AddRecord()
        {
            var txtName = document.querySelector('#txtName');
            __doPostBack(upMainID, txtName.value);

            return false;
        }
    </script>
</body>
</html>

The JS code statement __doPostBack(upMainID, txtName.value); is doing all the magic here. The implementation of __doPostBack is as follows:

JavaScript
function __doPostBack(eventTarget, eventArgument) {
    if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
        theForm.__EVENTTARGET.value = eventTarget;
        theForm.__EVENTARGUMENT.value = eventArgument;
        theForm.submit();
    }
}

In the above, we have to pass two arguments, the eventArgument being the optional one.

Image 1

Add the methods in the .cs file to wire up the data binding event. We will be creating the test data from the code and there is a method named SetData for that purpose. AddRecord method is used to add a new record to our table. This table is stored in the application session state so that we can retrieve this information in subsequent partial postbacks.

SingleGrid.aspx.cs

C#
namespace WebApplication1
{
    public partial class SingleGrid : System.Web.UI.Page
    {
        private DataTable _table;

        /// <summary>
        /// Page load event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                SetData();
            }
            else
            {
                String parameter = Request["__EVENTARGUMENT"];
                AddRecord(parameter);                
            }
            gridMain.DataBind();
        }

        /// <summary>
        /// Grid data binding event
        /// </summary>
        /// <param name="e"></param>
        protected void gridMain_DataBinding(object sender, EventArgs e)
        {
            gridMain.DataSource = (Session["MainTable"] as DataTable).DefaultView;
        }

        /// <summary>
        /// Sets the sample data table
        /// </summary>
        /// <returns></returns>
        private void SetData()
        {
            _table = new DataTable();
            _table.Columns.Add("ID", typeof(Int32));
            _table.Columns.Add("Name", typeof(String));

            _table.Rows.Add(1, "David");
            _table.Rows.Add(1, "Mark");
            Session["MainTable"] = _table;
        }

        /// <summary>
        /// Adds a new student record
        /// </summary>
        /// <param name="name"></param>
        private void AddRecord(String name)
        {
            DataTable table = Session["MainTable"] as DataTable;
            table.Rows.Add(table.Rows.Count + 1, name);
        }
    }
}

Whenever the page load event is fired, it is being checked if it's a postback or not. Depending on the outcome of that check, we have to either load the data for the first time or add a new record to the existing collection. We are passing the record Name value as an event argument when we are executing the __doPostBack from the client JavaScript. This argument's value is captured on the server side and then is being used to add a new record.

To see how this example is working, just execute the Web Form, enter a Name and click on the Add link and you will see that a new name is added to our existing data and the grid also refreshes.

Let's move on to a more involved and challenging scenario where we need to make async updates in multiple grids.

Multiple Grid View Controls

In this example, we will update a couple of grid controls based on any input's event. A drop down input will contain a list of countries and two grid controls will contain State and City list. The code for this example is somewhat complex than the previous example as we will utilize the page events of ASP.NET Ajax. There are two ways in which we are going to update the grids:

  1. Updating grids when another input is changed
  2. Updating second grid based on the user's interaction with the first grid

This example uses test data tables in the code to create static data. You can modify the sample code to connect the grids to an existing data source. Add a new webpage to our existing application and name it MultipleGrids.aspx. Add a drop down input and name it cboCountryList which will contain a list of countries. Whenever the value in the input changes, we will need to refresh the grids with the correct data. We don't need to have the drop down input as a server control but if you want to fill it from the server code with a list, then it needs to have runat='server' attribute.

Add two Grid View controls within their own Update Panels so that we can update them asynchronously. Now the problem that most of us face is how to handle the async update of multiple grid views because it is not straight forward to refresh them from JavaScript in the first place and it is weird to subscribe to their update complete event. Things get even more serious when we have to implement a promise like code structure to chain update panel refreshes.

To update multiple grids, we need to add ASP.NET Ajax page loaded event handler.

Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(pageLoaded);

In the above, the pageLoaded function will always get called for complete and partial page loads. This is the exact place where we need to execute any code that needs to be called after the update panel is finished refreshing its contents.

The following code is for the MultipleGrids.aspx:

ASP.NET
<%@ Page Language="C#" AutoEventWireup="true" 
    CodeBehind="MultipleGrids.aspx.cs" Inherits="WebApplication1.MultipleGrids" %>

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <script src="https://code.jquery.com/jquery-1.12.0.min.js" type="text/javascript"></script>
</head>
<body>
    <form id="Form1" runat="server">
        <asp:ScriptManager ID="scriptManager1" runat="server" 
         AsyncPostBackTimeout="90"></asp:ScriptManager>

        <select ID="cboCountryList">
            <option value="0" selected>United States</option>
            <option value="1">India</option>
        </select>

        <h3>States</h3>
        <asp:UpdatePanel ID="upMain1" runat="server" 
         UpdateMode="Conditional" AutoGenerateColumns="false">
            <ContentTemplate>
                <asp:GridView ID="gridMain1" runat="server" 
                 OnDataBinding="gridMain1_DataBinding" 
                 OnRowDataBound="gridMain1_RowBound" CellPadding="10">
                </asp:GridView>
            </ContentTemplate>
        </asp:UpdatePanel>
        <br />
        <h3>Cities</h3>
        <asp:UpdatePanel ID="upMain2" runat="server" 
         UpdateMode="Conditional" AutoGenerateColumns="false">
            <ContentTemplate>
                <asp:GridView ID="gridMain2" runat="server" 
                 OnDataBinding="gridMain2_DataBinding" CellPadding="10">
                </asp:GridView>
            </ContentTemplate>
        </asp:UpdatePanel>
    </form>

    <script>
        Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(pageLoaded);

        var upMain1ID = '<%= upMain1.ClientID%>';
        var upMain2ID = '<%= upMain2.ClientID%>';
        var gridMain1ID = '<%= gridMain1.ClientID%>';
        var cboCountryList = null;
        document.addEventListener("DOMContentLoaded", onLoad);

        function onLoad()
        {
            cboCountryList = document.querySelector('#cboCountryList');
            cboCountryList.addEventListener('change', UpdateCountry);
            loadGridMain1();
        }
        
        function loadGridMain1()
        {
            $('#' + gridMain1ID).find('[gridmain1row="true"]').each(function (idx, el)
            {
                el.onclick = function ()
                {
                    var stateID = $(el).attr('StateID');
                    return function ()
                    {
                        grid1RowClick(stateID);
                    }
                }();
            });
        }

        function grid1RowClick(stateID)
        {
            __doPostBack(upMain2ID, stateID);
        }

        function pageLoaded(sender, args)
        {
            var prm = Sys.WebForms.PageRequestManager.getInstance();
            if (prm.get_isInAsyncPostBack())
            {
                // get our array of update panels that were updated during the request
                var updatedPanels = args.get_panelsUpdated();
                for (var x = 0; x < updatedPanels.length; x++)
                {
                    var panel = updatedPanels[x].id;
                    switch (panel)
                    {
                        case upMain1ID:
                            loadGridMain1();
                            __doPostBack(upMain2ID, '');
                            break;
                    }
                }
            }
        }

        function UpdateCountry()
        {
            __doPostBack(upMain1ID, cboCountryList.value);

            return false;
        }
    </script>
</body>
</html>

When the drop down input value is changed, then all the grids are refreshing one by one. Also, when we select an item in the first grid, then the second grid refreshes. There are functions which are wired up with the change and click events. UpdateCountry will be called when the country drop down changes its selected value, and grid1RowClick will be called when any row in the first grid is clicked. Event binding of the HTML elements inside a grid needs to be handled both on the server and the client side. On the server side, we have to add custom attributes in the grid's Row Bound event. Later when the page loads in the browser, we can identify the grid elements based on those attributes we added and thus we can wire up those DOM elements with the JavaScript functions.

The first grid will have the State list of the selected country and the second grid will have the City list of the selected State. In the pageLoaded function, I have used PageRequestManager.get_isInAsyncPostBack() to check if the current page load is partial or not. Based on that, we need to identify the update panel that was refreshed to proceed chaining the subsequent code blocks. So when the first update panel is finished loading, then we are calling __doPostBack for the second grid's update panel. We cannot make async updates of multiple update panels simultaneously; doing that will prompt the JavaScript to throw a Parameter Count Mismatch error.

Let's move on to the server code now. Add the following code to the MultipleGrids.aspx.cs file:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;

namespace WebApplication1
{
    public enum Countries
    {
        UnitedStates = 0,
        India = 1
    }

    public partial class MultipleGrids : System.Web.UI.Page
    {
        private DataTable _stateTable;
        private DataTable _cityTable;

        /// <summary>
        /// Page load event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                Session["SelectedCountry"] = 0;
                Session["SelectedState"] = 5;
                SetData();                
            }
            else
            {
                String target = Request["__EVENTTARGET"];
                String parameter = Request["__EVENTARGUMENT"];
                if (target == "upMain1")
                {
                    Session["SelectedCountry"] = Convert.ToInt32(parameter);
                    switch ((Countries)Session["SelectedCountry"])
                    {
                        case Countries.UnitedStates:
                            Session["SelectedState"] = 5;
                            break;

                        case Countries.India:
                            Session["SelectedState"] = 1;
                            break;
                    }                    
                }
                else if (target == "upMain2" && parameter.Length > 0)
                {
                    Session["SelectedState"] = Convert.ToInt32(parameter);
                }
            }
            gridMain1.DataBind();
            gridMain2.DataBind();
        }

        /// <summary>
        /// Grid data binding event
        /// </summary>
        /// <param name="e"></param>
        protected void gridMain1_DataBinding(object sender, EventArgs e)
        {
            (Session["StateTable"] as DataTable).DefaultView.RowFilter = 
                String.Format("CountryID = '{0}'", (Int32)Session["SelectedCountry"]);
            gridMain1.DataSource = (Session["StateTable"] as DataTable).DefaultView;
        }

        /// <summary>
        /// Grid data binding event
        /// </summary>
        /// <param name="e"></param>
        protected void gridMain2_DataBinding(object sender, EventArgs e)
        {
            (Session["CityTable"] as DataTable).DefaultView.RowFilter = 
                String.Format("StateID = '{0}'", (Int32)Session["SelectedState"]);
            gridMain2.DataSource = (Session["CityTable"] as DataTable).DefaultView;
        }

        /// <summary>
        /// Sets the initial data.
        /// </summary>
        private void SetData()
        {
            _stateTable = new DataTable();
            _cityTable = new DataTable();

            //State table
            _stateTable.Columns.Add("CountryID", typeof(Int32));
            _stateTable.Columns.Add("StateID", typeof(Int32));
            _stateTable.Columns.Add("State", typeof(String));
            _stateTable.Columns.Add("Area", typeof(String));

            //City table
            _cityTable.Columns.Add("StateID", typeof(Int32));
            _cityTable.Columns.Add("City", typeof(String));
            _cityTable.Columns.Add("Population", typeof(String));

            #region India
            ...
            #endregion India

            #region United States
            ...
            #endregion United States

            Session["StateTable"] = _stateTable;
            Session["CityTable"] = _cityTable;
        }

        /// <summary>
        /// Row bound event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void gridMain1_RowBound(object sender, GridViewRowEventArgs e)
        {
            if (e.Row.RowType == DataControlRowType.DataRow)
            {
                DataRowView row = e.Row.DataItem as DataRowView;

                e.Row.Attributes.Add("gridmain1row", "true");
                e.Row.Attributes.Add("StateID", row["StateID"].ToString());
            }
        }
    }
}

The above code is mostly working same as the first example. We are updating the state of the grids stored in the application session state based on the event arguments. After that, the test data rebinds with the grids. Because the update panel re-renders its enclosing content, the grid is refreshed as a result.

Image 2

As mentioned before, the gridMain1_RowBound event is used to add custom attribute gridmain1Row to each grid row. The argument StateID is also being added as an attribute so that each row can have its own state information that can be later passed on to the JavaScript event function. In the client code, we have to find each row element which has the gridmain1Row attribute using jQuery and then loop out each of those elements to bind them to a JavaScript closure.

To test the code, change the selected country and both grids will refresh one after the other. Click on any State grid's row and the city grid will refresh to show the correct data.

Points of Interest

The code sample is attached for you to see and play with. There can be scenarios when the grids can reside in separate ASP.NET page controls. In order to execute a chained update panel refresh in this case, we will need to register our events to a global event system such that all the user controls have access to that event system and we can execute cross control code without breaking any modules.

History

  • 26th January, 2016: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Nitij
Software Developer (Senior)
India India
Just a regular guy interesting in programming, gaming and a lot of other stuff Smile | :)

Please take a moment to visit my YouTube Channel and subscribe to it if you like its contents!
My YouTube Channel

Don't be a stranger! Say Hi!!

Cheers!

Comments and Discussions

 
QuestionThe vote is 5 Pin
skydger1-Feb-16 20:06
Memberskydger1-Feb-16 20:06 
AnswerRe: The vote is 5 Pin
Nitij2-Feb-16 7:46
professionalNitij2-Feb-16 7:46 
GeneralMy vote of 5 Pin
Santhakumar M28-Jan-16 19:23
professionalSanthakumar M28-Jan-16 19:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.