Wednesday, May 20, 2009

Maintain Viewstate for Dynamic controls across the postback

       Most of us have faced the case when we have to load the controls dynamically. We can load user control by calling LoadControl method. To load inbuilt server controls we can use Page.Form.Controls.Add() method or we can user any container like panel or place holder and call Controls.Add() method. Upto this it looks very easy and works fine. However at first postback you either find the control is not loaded or the viewstate is not preserved for that control.

        Lets take an example, we have a user control with only one textbox inside it and a web for which have two buttons LoadControl and Submit. When user clicks on LoadControl button we will load the user control and on Submit button we just submit the form so that postback occurs.

   1: <%@ Control Language="C#" AutoEventWireup="true" CodeFile="ucTestControl.ascx.cs"
   2:      Inherits="ucTestControl" %>
   3: <asp:TextBox ID="txtName" runat="server"></asp:TextBox>

Fig - (1) User control with only a textbox.



   1: <%@ Page Language="C#" AutoEventWireup="true" 
   2: CodeFile="LoadUserConrtolDynamically.aspx.cs"
   3:     Inherits="LoadUserConrtolDynamically" %>
   4:  
   5: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
   6: "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   7: <html xmlns="http://www.w3.org/1999/xhtml">
   8: <head runat="server">
   9:     <title>Dynamically Load User Control</title>
  10: </head>
  11: <body>
  12:     <form id="form1" runat="server">
  13:     <div>       
  14:         <table>            
  15:             <tr>
  16:                 <td colspan="2">
  17:                     Click on "Load Control" button to load user control dynamically.
  18:                 </td>
  19:             </tr>
  20:             <tr>
  21:                 <td>
  22:                     <asp:Button ID="btnLoad" runat="server" Text="Load Control" OnClick="btnLoad_Click" />
  23:                 </td>
  24:                 <td>
  25:                     <asp:Button ID="btnSubmit" runat="server" Text="Submit" OnClick="btnSubmit_Click" />
  26:                 </td>
  27:             </tr>
  28:             <tr>
  29:                 <td colspan="2">
  30:                     Enter Name:
  31:                 </td>                
  32:             </tr>
  33:         </table>
  34:     </div>
  35:     </form>
  36: </body>
  37: </html>

Fig - (2) Webform with two buttons. 



   1: protected void btnSubmit_Click(object sender, EventArgs e)
   2: {
   3:     
   4: }
   5:  
   6: protected void btnLoad_Click(object sender, EventArgs e)
   7: {
   8:     AddUserControl();
   9: }
  10:  
  11: private void AddUserControl()
  12: {
  13:     UserControl ucTest = (UserControl)LoadControl("~/ucTestControl.ascx");
  14:     Page.Form.Controls.Add(ucTest);
  15:     IsControlAdded = true;
  16: }

Fig - (3) Code behind for Webform


          As you can see we are calling LoadControl() method to load our user control. Now when you click on submit button and you find that the control disappears.  WHY? The reason is as we have loaded the control the page's control tree does not have its detail so we need to load the control again with each postback. So first rule for lading dynamic control is,


RULE 1 : Load the Dynamic control in each postback


        So in our example we also need to add the control with each postback. However we need to check that whether the control is loaded before postback or not. To check this I have created one property as shown in code below,



   1: public bool IsControlAdded
   2: {
   3:     get
   4:     {
   5:         if (ViewState["IsControlAdded"] == null)
   6:             ViewState["IsControlAdded"] = false;
   7:  
   8:         return (bool)ViewState["IsControlAdded"];
   9:  
  10:     }
  11:     set
  12:     {
  13:         ViewState["IsControlAdded"] = value;
  14:     }
  15: }
  16:  
  17: protected void Page_Load(object sender, EventArgs e)
  18: {
  19:     if(IsControlAdded)
  20:         AddUserControl();
  21: }
  22:  
  23: protected void btnSubmit_Click(object sender, EventArgs e)
  24: {        
  25: }
  26:  
  27: protected void btnLoad_Click(object sender, EventArgs e)
  28: {
  29:     if(!IsControlAdded)
  30:         AddUserControl();
  31: }
  32:  
  33: private void AddUserControl()
  34: {
  35:     UserControl ucTest = (UserControl)LoadControl("~/ucTestControl.ascx");
  36:     Page.Form.Controls.Add(ucTest);
  37:     IsControlAdded = true;
  38: }

Fig - (4) loading dynamic control with each postback.


         You may have notice the code the code for lading user control,



   1: UserControl ucTest = (UserControl)LoadControl("~/ucTestControl.ascx");
   2: Page.Form.Controls.Add(ucTest);

Fig - (5) Code for loading User control Dynamically


        You may also write this two line code in single line as shown below,



   1: Page.Form.Controls.Add(LoadControl("~/ucTestControl.ascx"));

Fig - (6) Code for loading Dynamic Control


        If you use this code to load user control dynamically than sometime you find an interesting issue. The viewstate is not maintained for dynamically loaded control !!!!!! The viewstate requires the controls ID to store the viewstate properly. So always use code shown in Fig - (4) to load user control dynamically.


RULE 2 : Always load the Dynamic control by assigning it to one temporary variable so that it has unique id.


       One more common mistake is we write Page.Controls.Add() to add the control. This will leads to "Control 'ControlName' of type 'ControlType' must be placed inside a form tag with runat=server" error.


       Now we have to find how the viewstate is maintained for dynamically loaded user control. I will suggest you to read this article first. When we add any control dynamically it play "catch-up" with the page life cycle once they are added. Once the control is added to the "Controls" collection, it plays "catch-up" with the page life cycle, and all the events that it missed are fired. This leads to a very important conclusion: you can add dynamic controls at any time during the page life cycle until the "PreRender" event. Even when you add the dynamic control in the "PreRender" event, once the control is added to the "Controls" collection, the "Init", "LoadViewState", "LoadPostbackdata", "Load", and "SaveViewstate" are fired for this control.


     We have seen how to load user controls dynamically now we will see how to load inbuilt server controls.  The process is same however we don't have to use LoadControl method, we can directly use Controls.Add() method as shown below,



   1: TextBox tempText = new TextBox();
   2: tempText.ID = LstTextBoxId.Count.ToString();
   3: // pnlTextBox is asp:Panel
   4: pnlTextBox.Controls.Add(tempText);

Fig - (7) Load server controls at runtime and maintaining viewstate


        Here we have added server controls and user controls dynamically using regular way. You can load user controls / server control dynamically using AJAX also. Below is the code where I have displayed loading controls using AJAX and normal way.



<%@ Page Language="C#" AutoEventWireup="true" 
CodeFile="LoadUserConrtolDynamically.aspx.cs"
    Inherits="LoadUserConrtolDynamically" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Dynamically Load User Control</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        </asp:ScriptManager>
        <table>
            <tr>
                <td colspan="2">
                    Click on "Add Textbox" button to add texbox dynamically.
                </td>
            </tr>
            <tr>
                <td>
                    <asp:Button ID="btnAddTextBox" runat="server" Text="Add Textbox" OnClick="btnAddTextBox_Click" />
                </td>
            </tr>
            <tr>
                <td colspan="2">
                    <asp:Panel ID="pnlTextBox" runat="server">
                    </asp:Panel>
                </td>
            </tr>
            <tr>
                <td colspan="2">
                    <asp:Label ID="lblTest" runat="server"></asp:Label>
                </td>
            </tr>
            <tr>
                <td colspan="2">
                    <br />
                </td>
            </tr>
            <tr>
                <td colspan="2">
                    Click on "Add Textbox using Ajax" button to add texbox dynamically with Ajax.
                </td>
            </tr>
            <tr>
                <td>
                    <asp:UpdatePanel ID="upnlLOadControl" runat="server">
                        <ContentTemplate>
                            <asp:Button ID="btnAddTextBoxAjax" runat="server" Text="Add Textbox using Ajax" OnClick="btnAddTextBoxAjax_Click" />
                            <asp:Panel ID="pnlAjaxTextbox" runat="server">
                            </asp:Panel>
                        </ContentTemplate>
                    </asp:UpdatePanel>
                </td>
            </tr>
            <tr>
                <td colspan="2">
                    <br />
                </td>
            </tr>
            <tr>
                <td colspan="2">
                    Click on "Load Control" button to load user control dynamically.
                </td>
            </tr>
            <tr>
                <td>
                    <asp:Button ID="btnLoad" runat="server" Text="Load Control" OnClick="btnLoad_Click" />
                </td>
                <td>
                    <asp:Button ID="btnSubmit" runat="server" Text="Submit" OnClick="btnSubmit_Click" />
                </td>
            </tr>
            <tr>
                <td colspan="2">
                    Enter Name:
                </td>                
            </tr>
        </table>
    </div>
    </form>
</body>
</html>

Fig - (8) code for aspx page.



using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
 
public partial class LoadUserConrtolDynamically : System.Web.UI.Page
{
    public bool IsControlAdded
    {
        get
        {
            if (ViewState["IsControlAdded"] == null)
                ViewState["IsControlAdded"] = false;
 
            return (bool)ViewState["IsControlAdded"];
 
        }
        set
        {
            ViewState["IsControlAdded"] = value;
        }
    }
 
    public List<String> LstTextBoxId
    {
        get
        {
            if (ViewState["LstTextBoxId"] == null)
                ViewState["LstTextBoxId"] = new List<String>();
 
            return (List<String>)ViewState["LstTextBoxId"];
 
        }
        set
        {
            ViewState["LstTextBoxId"] = value;
        }
    }
 
    public List<String> LstAjaxTextBoxId
    {
        get
        {
            if (ViewState["LstAjaxTextBoxId"] == null)
                ViewState["LstAjaxTextBoxId"] = new List<String>();
 
            return (List<String>)ViewState["LstAjaxTextBoxId"];
 
        }
        set
        {
            ViewState["LstAjaxTextBoxId"] = value;
        }
    }
 
    protected void Page_Load(object sender, EventArgs e)
    {
        if(IsControlAdded)
            AddUserControl();
 
        if (LstTextBoxId.Count > 0)
            AddTextBoxes(pnlTextBox, LstTextBoxId);
 
        if (LstAjaxTextBoxId.Count > 0)
            AddTextBoxes(pnlAjaxTextbox, LstAjaxTextBoxId);
    }
 
    protected void btnSubmit_Click(object sender, EventArgs e)
    {        
    }
 
    protected void btnLoad_Click(object sender, EventArgs e)
    {
        if(!IsControlAdded)
            AddUserControl();
    }
 
    private void AddUserControl()
    {
        UserControl ucTest = (UserControl)LoadControl("~/ucTestControl.ascx");
        Page.Form.Controls.Add(ucTest);
        IsControlAdded = true;
    }
 
    private void AddTextBoxes(Panel pnlTemp,List<String> lstTemp)
    {
        TextBox tempText;
        for (int i = 0; i < lstTemp.Count; i++)
        {
            tempText = new TextBox();
            tempText.ID = lstTemp[i];
            pnlTemp.Controls.Add(tempText);
        }
    }
 
    protected void btnAddTextBox_Click(object sender, EventArgs e)
    { 
        TextBox tempText = new TextBox();
        tempText.ID = LstTextBoxId.Count.ToString();
        pnlTextBox.Controls.Add(tempText);
        LstTextBoxId.Add(LstTextBoxId.Count.ToString());
    }
 
    protected void btnAddTextBoxAjax_Click(object sender, EventArgs e)
    {
        TextBox tempText = new TextBox();
        tempText.ID = Guid.NewGuid().ToString();
        pnlAjaxTextbox.Controls.Add(tempText);
        LstAjaxTextBoxId.Add(tempText.ID);
    }
}

Fig - (9) Code for aspx.cs page



<%@ Control Language="C#" AutoEventWireup="true" 
CodeFile="ucTestControl.ascx.cs" Inherits="ucTestControl" %>
<asp:TextBox ID="txtName" runat="server"></asp:TextBox>

Fig - (10) Code for user control


 


Happy Programming !!!