Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Improving ASP.Net containing lengthy Page_Loads

0.00/5 (No votes)
16 Feb 2016 1  
Replacing slow Page_Load functions with UpdatePanels and asynchronous data loading

Introduction

It happened to me again recently.

One of my colleagues showed me a legacy ASP.Net application, and wanted to know how to fix a nasty issue with it.  Their webpage would start to get displayed, but the pulldown menu would appear on the screen – half-drawn, and in a mess - and freeze there for 6-7 seconds, until suddenly the page sorted itself out, and displayed itself properly.

The cause ? 

The developer had decided to put all of their time-consuming code to load SQL Server data into their Page_Load function.  And once that started running, the browser would grind to a halt until the data had finished loading.

In this article, I'll take you through the simple steps to get rid of this problem, by moving the time-consuming code into a separate thread, then getting the webpage to update itself once the data is ready.

Disclaimer

It goes without saying, it's much more efficient and maintainable to use a modern technology like Angular to load and display data, as I have described in my other articles, but this article is a simple step-by-step guide to quickly improve legacy pages, without having to rewrite everything.

Follow these steps, and with very little effort, you'll make your slow, painful webpages much more responsive, and get you a payrise when you have your next annual review.  Perhaps.

Legacy code

Let's start with the how the code used to look like.

Basically, our developer had put his code, to load data from SQL Server, in the Page_Load function.   

We’ll simulate this problem by creating a 10 second pause, then generating a sample DataTable, and setting the DataGrid to use the DataTable as its data source.

protected void Page_Load(object sender, EventArgs e)
{
    if (IsPostBack)
        return;

    //  Wait for 10 seconds...
    System.Threading.Thread.Sleep(10000);

    //  .. then create a DataTable containing some sample data...
    System.Data.DataTable dt = new System.Data.DataTable("Drivers");

    dt.Columns.Add("UserID", Type.GetType("System.Int64"));
    dt.Columns.Add("Surname", Type.GetType("System.String"));
    dt.Columns.Add("Forename", Type.GetType("System.String"));
    dt.Columns.Add("Sex", Type.GetType("System.String"));
    dt.Columns.Add("Date of Birth", Type.GetType("System.DateTime"));

    dt.Rows.Add(new object[] { 1, "James", "Spencer", "M", new DateTime(1962, 3, 19) });
    dt.Rows.Add(new object[] { 2, "Edward", "Jones", "M", new DateTime(1939, 7, 12) });
    dt.Rows.Add(new object[] { 3, "Janet", "Spender", "F", new DateTime(1996, 1, 7) });
    dt.Rows.Add(new object[] { 4, "Maria", "Percy", "F", null });
    dt.Rows.Add(new object[] { 5, "Malcolm", "Marvelous", "M", new DateTime(1973, 5, 7) });

    //  ...and bind it to our ASP.Net GridView control.
    this.grid.DataSource = dt;
    this.grid.DataBind();
}

The result of this code was that the webpage was painfully slow to appear.  Usually the browser would get so far in displaying the page, then freeze for several seconds, before finally managing to display the complete webpage.

Meanwhile, the user has been staring at their browser, wondering if it’s crashed, or if their laptop has hung.

It's not a pleasant experience.

Let’s make it async !

Okay, so let’s make it better.

The first step is to go into your .aspx file, and locate which section of your webpage contains the controls that you’ll want to update when your time-consuming task finishes.

In our case, this is simple, only our DataGrid will need to be updated once our data load has completed.

<asp:DataGrid ID="grid" runat="server"></asp:DataGrid>

What we need to do is wrap this control (or group of controls) inside an UpdatePanel and ContentTemplate, and we need to add a Timer control to our page.

<asp:UpdatePanel ID="panel" runat="server" UpdateMode="Conditional">
    <ContentTemplate>
        <asp:Timer ID="MyTimer" OnTick="timer_tick" Interval="1000" runat="server" />



        <!--  Put your controls that you will need updating, here.. -->
        <asp:DataGrid ID="grid" runat="server"></asp:DataGrid>



    </ContentTemplate>
</asp:UpdatePanel>

Our aim is to get the webpage onto the screen as quickly as possible, and once our data is loaded, we can go back and update (just) this section of the webpage.

Our next step is to separate out the data-loading code from our Page_Load function into its own function, and to make it populate a variable (a DataTable in this example) which is stored in a Session variable. 

You’ll need one of these Session variables for each chunk of data which you’ll be loading asynchronously.

We will also need a boolean Session variable, bReadyToDisplayData, which we’ll set to “true” when our data has finished loading.

System.Data.DataTable dt
{
    get
    {
        return (System.Data.DataTable)Session["table1"];
    }
    set
    {
        Session["table1"] = value;
    }
}
bool bReadyToDisplayData
{
    get
    {
        return (bool)Session["bReadyToDisplayData"];
    }
    set
    {
        Session["bReadyToDisplayData"] = value;
    }
}

protected void Page_Load(object sender, EventArgs e)
{
    if (IsPostBack)
        return;

   bReadyToDisplayData = false;

   LoadDataFromWebService();
}

private void LoadDataFromWebService()
{
    //  (This would be our time-consuming function which loads data from some web service.)

    //  Wait for 10 seconds...
    System.Threading.Thread.Sleep(10000);

    //  .. then create a DataTable containing some sample data...
    dt = new System.Data.DataTable("Drivers");

    //  ... etc ...


    //  Note: We no longer bind our DataTable to our control in this function.


    //  Once all of our data has been loaded, we set this boolean variable, which will
    //  trigger our DataGrid controls to be displayed, showing our freshly-loaded data.
    bReadyToDisplayData = true;
}

This looks better, but we are still calling our data-loading function synchronously, so next, lets change Page_Load to call it asynchronously..

System.Threading.Thread thread = new System.Threading.Thread(LoadDataFromWebService);
thread.Start();

There’s just one last thing to add, to tie this all together.

You’ll have noticed that we added a Timer control to our .aspx file.  This is to allow us to periodically check whether our data has finished loading, and when it has loaded, to populate our grid controls.

To do this, we need a Timer "tick” handler.

(Without this Timer variable, we wouldn’t have a method of updating our UpdatePanel.)

protected void timer_tick(object sender, EventArgs e)
{
    //  Every second, our webpage will call this function.
    //  If our background thread to load some JSON data has finished running, then 
    //  we'll want to display the data in a grid, and can then stop the timer.
    //
    if (bReadyToDisplayData == false)
    {
        return;     //  Our background thread is still running.
    }


    //  Our JSON data has finished loading !


    //  Populate our Grid with the JSON data
    this.grid.DataSource = dt;
    this.grid.DataBind();

    //  We can now update our UpdatePanel, and stop the timer.  Our webpage is now complete!
    this.panel.Update();                
    MyTimer.Enabled = false;
}

And that’s it !

Now, when you open this aspx webpage, it’ll get displayed really quickly, go off and load it’s data, and when it’s finished, we’ll set the DataGrid’s DataSource to point to this data, and get it to display it.

Session variables

Just a quick note on using Session variables. 

If you find that this code doesn't work for you, check whether your .aspx page is managing to save Session variable values.   To do this, put a breakpoint in your Page_Load function, just after you've set the bReadyToDisplayData variable.    

protected void Page_Load(object sender, EventArgs e)
{
    if (IsPostBack)
        return;

    bReadyToDisplayData = false;

    //  Put a breakpoint on the following line...
    System.Threading.Thread thread = new System.Threading.Thread(LoadSomeData);

If you then examine the Session variable, its "Count" value should be at least 1  (as we've just added a Session variable to store the bReadyToDisplayData variable).

If this Count value is 0, then your webpage isn't storing Session variables, and this code won't work properly.

Two things to check:

1. Does your Server Name contain underscore characters ?  Apparently, this can cause problems with IIS.

2. Try adding the following lines into your Global.asax file:

protected void Session_Load(object sender, EventArgs e)
{
    Session["info"] = 1;
}

This Session variable problem seems to have been noticed mainly on Internet Explorer 11.

Summary

And, that’s it.  A simple walkthrough of taking some pretty painful code, and making it more user friendly.

I’ve seen plenty of in-house webpages suffering from this problem, particularly when dealing with large quantities of data.  Often the user would click on a link to open the webpage, and after 30 seconds of watching the hourglass going around, would give up and go elsewhere.

Now, we can easily modify such code, to make the code more responsive.

And you could, of course, take this further, perhaps by adding a “Please wait” message inside of your UpdatePanel, which gets hidden once the data has finished loading.

Personally, I like showing a timer on the screen when I know my webpage will take a few seconds to prepare the data it needs to show.  Users tend to trust webpages more, when they can actually see a timer counting the seconds.   It’s a much friendlier experience than a browser window which just looks “locked”.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here