Using Classic ASP to avoid performance problems with ASP.NET Dynamic controls

First off I must admit that I’m not the biggest fan of ASP.NET and you’ll often find me accusing it of attempting to shoe-horn an old VB6 style event model to the web. However, I do concede that it is a very good attempt at doing just that and helping to take away much of the plumbing work that was required to write a decent classic ASP application, one of these issues was ‘solved’ by Viewstate. Love it or hate it viewstate is an easy way to maintain the values of the HTML form elements between post-backs. So everything is Ok then? Well I’m conveniently ignoring the size issues to talk about a common pattern of using a single page and changing the view of the page by using dynamically created controls. Typically the single page has no content of its own but some stimulus, such as the query string, allows the code-behind to create completely different views of data. I’ll stop short of saying it’s a way to implement MVC/MVP patterns but you could do it.

An Example

So as an example consider creating an application that allows the user to search for employee details, by selecting from a set of criteria. Once selected the user presses the "show me the results" button and the page takes the criteria and displays details. The user can then change the details and update them or move back to the criteria page. So introduces our first problem, what details to display for the employees?

Displaying details that are only known at run-time

Ok so it is a bit contrived, but let’s say that the number of text boxes change due to the type of employee. The code runs a fairly expensive SQL query, that takes 30 seconds to complete, in order to discover the employee details to display. But we need to be careful where to run this query in order to correctly construct the controls for ASP.NET to use. The basic part of the page life-cycle to create dynamic controls is in the OnInit override. Here are some snippets, I know there is code in there I’ve not explained, hopefully later you’ll see how I’ve populated those…

protected override void OnInit(EventArgs e)

        { 


            base.OnInit(e);


            // Add dynamic controls here


            CreateView(); 


        }


private void CreateView()
        {
            if (this.lastView == this.currentView)
            {
                // do nothing, rely on the view state populating the fields
                return;
            }
            // not the same view, so trash whatever went before
           
// (probably nothing yet but just to be safe)
            this.PlaceHolderDynamicContent.Controls.Clear();

            if (this.currentView == 1)
            {
                CreateView1Controls();
            }
            if (this.currentView == 2)
            {
                                      // Warning, expensive discovery query in here
                CreateView2Controls();
            } 
        }

Ok so we’ve created the dynamic controls, but how do you read the changes the end-user has entered?

Reading changes made to dynamic controls

Leaving the well trodden road of static controls can be tricky, to read data from dynamically created controls in a post-back you must do so after ASP.NET has; a) Create the control hierarchy used to create the previously rendered page b) inserted the values into the controls from the viewstate and post data. The basic place to read the data is the page_load event.

protected void Page_Load(object sender, EventArgs e)
        {
                // Read saved data from dynamic controls here
                SaveLastView();
        }

So we can create a view that was unknown at design time and read the data from those dynamic controls so what’s the problem?

Running expensive discovery queries on post-back

As we’ve seen in order to read the data from dynamic controls we have to help ASP.NET out by creating the initial set of controls during post-back. However, to do that we’ll have to re-run that expensive discovery query again. If the user has changed some details then it’s an expense we’ll have to put up with (or use some other caching mechanism). However, what if the user hasn’t made any changes and want to return to the Criteria view? Currently we’d blindly run the discovery query and incur 30 sec hit only to throw away all the controls and create the control set for the criteria…seems a bit of waste. So how can we know that the user has navigated away from view when we can only read the data in the Page_Load, but that happens after the Page Initialize and therefore after we’ve run the discovery query! Well this is where classic ASP can come to the rescue.

Classic ASP rides to the rescue

The ASP.NET page life-cycle isn’t magic, the browser posts data to the server, ASP.NET process the data and transforms it into the event based model. There is lot of smoke and mirrors going on but the underlying process hasn’t changed from classic ASP, the Response object still contains the user’s posted data. So if we have a navigation control called MyButtonView1 then you can fish directly into the Response object and get the value via Response.Form["MyButtonView1"]. This means that in the Initialize event we can know if the user is navigating away and therefore we don’t have to run the discovery query for the details. Hurray all the problems solved? No, what happens if the user has made some changes and then navigated away? I knew you’d ask that. Well this is where it becomes irritating, because you have to write more an more code to support the dynamic controls reaching a point where you may as well write classic ASP from the off. Oh well, here is one way to do this. Add client side OnChange to the dynamic controls that update a single "HiddenFieldNeedsToSave" control, then in the Init you can check this too. So finally we’ve got a mechanism to support dynamic controls without having to needless re-run expensive discovery queries.

protected override void OnInit(EventArgs e)
        {
            this.lastView = Convert.ToInt32(Request["HiddenFieldView"]);
            if (Request["ButtonView1"] == "View1")
            {
                this.currentView = 1;
            }

            if (Request["ButtonView2"] == "View2")
            {
                this.currentView = 2;
            }

            if (Request["ButtonSave"] == "Save")
            {
                this.isSaving = true;
                this.currentView = this.lastView;
            }
            base.OnInit(e);

            // Add dynamic controls here
            if (this.isSaving)
            {
                CreateLastView();
            }
            else
            {
                CreateView();
            }
        }


 protected void Page_Load(object sender, EventArgs e)
        {
            if (this.isSaving)
            {
                // Read saved data from dynamic controls here
                SaveLastView();
                this.isSaving = false;
                CreateView();
            }
            this.HiddenFieldView.Value = Convert.ToString(this.currentView);
        }

Hopefully I’ve missed something and some nice person can show me the error of my ways, but until then my way of solving this ASP.NET problem is to turn to classic ASP…or just switch to using the MVC project 😉
   

One thought on “Using Classic ASP to avoid performance problems with ASP.NET Dynamic controls

  1. Alastair August 17, 2008 / 12:53 pm

    How about using a repeater control.  You could define an item template with the 30 textboxes.  Then on the ItemDataBound event, hide the controls that shouldn\’t be displayed for this record – presuming we can do this given the data you get back?  On the postback, we dont get the data back, and just rely on the Repeater control to rebuild the control hierarchy (which it will do automatically based on the number of items it previously rendered – it stores this in the view state).  You can then retrieve control values from the repeater in order to perform any save.The code might look like – protected void Page_Init(object sender, EventArgs e){  // SETUP REPEATER AND ITEM TEMPLATE}protected void Page_Load(object sender, EventArgs e)    {        if (!IsPostBack)        {            // Do data retrieval            data = SLOW QUERY;            Repeater1.DataSource = data ;            Repeater1.DataBind();        }    }    protected void Repeater1_DataBound(Object Sender, RepeaterItemEventArgs e)    {        // Hide the unnecessary textboxes for this row    }    protected void Button1_Click(object sender, EventArgs e)    {        foreach (RepeaterItem item in Repeater1.Items)        {            TextBox textBoxValue = (TextBox)item.FindControl("TextBox1");            // DO YOUR SAVE        }    }

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s