Apr 27 2008

Load Controls Dynamically in ASP.NET

Category:Bil@l @ 11:44

Download Code 

Abstract
In this article, I will show you one of the ways you can use to handle adding controls dynamically in an ASP.NET Page.

Problem
Assume that you have an ASPX page that contains a button called "Add New" and you want every time the user clicks that button to go to the server, create a new instance of a UserControl, and then adds the control instance into the page. The reason why I mentioned *a UserControl* is that it is easier to group a set of controls into a single UserControl so that when you want to add that set of controls, you would simply instantiate a new instance of the UserControl.

Solution Design
My way of solving this problem is by having a PlaceHolder located on the ASPX page. This place holder shall hold all the controls that have been added dynamically. In addition, the *Page Controller* design pattern is to be used such that the BasePage class shall hold all the required methods to add a new instance of a control into the page. Which means, any ASPX page that wants to handle dynamically adding controls it can simply inherit from this BasePage class and that's it!!
More over, the solution shall also take into consideration that the ASPX page might include several place holders and not only one. In other words, you can handle adding contrls dynamically into several areas on the page.

Solution
To start with, create a new Web Site in VS 2008.

Once the website is created, add a new BasePage.cs class. In that class add the following first section:

    // Holds the Session Key to store the added-controls on page
    private const string SESSION_CONTAINERS_KEY = "Controls_In_Container";
   
    // Holds the path to a user control
    private const string PATH_TO_CONTROL = "~/App_Controls/{0}";

The SESSION_CONTAINERS_KEY holds the key value that will be used as a session key. More on this later. The PATH_TO_CONTROL contains the virtual path in the application that contains the usercontrols you will usually load into the ASPX pages.

Now add the following property:

    /// <summary>
    /// Holds a dictionary of all the containers defined on
    /// the page that are used as place holders for dynamically
    /// added controls. This property is a dictionary with the
    /// place holder's ClientID serving as the key, and as value
    /// a dictionary with the key as a UserControl's ClientID
    /// and value of the  UserControl itself.
    /// This property checks to see if there are any loaded containers
    /// in the Session variable, if not it creates a new instance
    /// of an empty Dictionary of containers
    /// </summary>
    protected Dictionary<string, Dictionary<string, UserControl>> Container
    {
        get
        {
            // Create a new instance of a dictionary
            // holding as Key the name of the Conatiner
            // and as Value another Dictionary. The inline Dictionary
            // has as Key the ClientID of the UserControl added to the page
            // and as Value the UserControl itself
            Dictionary<string, Dictionary<string, UserControl>> containers =
                new Dictionary<string, Dictionary<string, UserControl>>();

            // If there are containers in the Session variable
            // load them
            if (Session[SESSION_CONTAINERS_KEY] != null)
                containers = (Dictionary<string, Dictionary<string, UserControl>>)Session[SESSION_CONTAINERS_KEY];

            return containers;
        }
    }
 

If you read the comments on the above property you would understand what the meaning of this property is. It will simply hold a dictionary of all the usercontrols that have been added into the page for every place holder. This means, it is a dictionary of dictionaries. There is a dictionary for all the place holders on the page, and every place holder has a dictionary of usercontrols that are added into the ASPX page.

A method is needed to create a new instance of a UserControl so that it can be added into the page. The method is called *CreateControlInstance* and is shown as follows:

    /// <summary>
    /// Creates a new instance of a Control defined
    /// by a name. This method takes as input the unique control
    /// key to set a unique ID property
    /// </summary>
    /// <param name="t"></param>
    /// <returns></returns>
    protected UserControl CreateControlInstance(string controlName, string controlKey)
    {
        // Create a new instance of the control
        UserControl newControl =
            (UserControl)LoadControl(string.Format(PATH_TO_CONTROL, controlName));

        // Set the ID of the new control instance
        newControl.ID = string.Format(controlKey, Guid.NewGuid().ToString());

        return newControl;
    }
 

It is very simple. It takes as input the control name and the ID string format to use. As you notice inside the body of the method, I am replacing the {0} field in the constant string defined above, PATH_TO_CONTROL, with the name of the UserControl. So you had above the PATH_TO_CONTROL as *~/App_Controls/{0}*, now by replacing {0} with the control name, you could have something as *~/App_Controls/MyControl.ascx".

In addition, the ID of the UserControl is calculated by replacing the {0} in the *controlKey* parameter with a Guid. For instance, you can pass as controlKey something as *MyControl_{0}*, and the {0} shall be replaced with a Guid and used as an ID for the UserControl.

Another important method is the *AddControlToContainer*. It is shown as follows:

    /// <summary>
    /// It takes as input the UserControl to add and a reference
    /// to the place holder on the page. It simply adds the passed
    /// in usercontrol and adds it to the container.
    /// </summary>
    /// <param name="c"></param>
    protected void AddControlToContainer(UserControl c, PlaceHolder phContainer)
    {
        // Define a dictionary of containers
        Dictionary<string, Dictionary<string, UserControl>> containers = null;

        // If there are defined containers with usercontrols in the session, get them
        if (Session[SESSION_CONTAINERS_KEY] != null)
            containers = (Dictionary<string, Dictionary<string, UserControl>>)Session[SESSION_CONTAINERS_KEY];
       
        // Seems there are no pre-defined containers containg usercontrols in the Session,
        // create a new dictionary
        if (containers == null)
            containers = new Dictionary<string, Dictionary<string, UserControl>>();

        // If this is the first time we are adding a Usercontrol to a container
        // add a record for this placeholder identified by the place holder's
        // ClientID.
        if (!containers.ContainsKey(phContainer.ClientID))
            containers.Add(phContainer.ClientID, new Dictionary<string, UserControl>());

        // Add to the specified container a new record
        // having as value the ClientID of the UserControl
        // and as value the UserControl itself.
        if (containers[phContainer.ClientID] != null)
            containers[phContainer.ClientID].Add(c.ClientID, c);

        // Update the session variable
        Session[SESSION_CONTAINERS_KEY] = containers;
    }

This method is very important. It takes as input the UserControl instance and the place holder where you want to add the UserControl. The first thing this method does is to check whether the list of containers together with their added controls are stored in an ASP.NET Session variable. Here you remember the SESSION_CONTAINERS_KEY constant string that I mentioned above. It is used as a retreival key for the Session variable.

If the Session variable is null, means this is the first time the user is trying to add a new UserControl. The code simply creates a new instance of the Container Dictionary-of-Dictionaries explained above.

The code also checks if the place holder to add the UserControl to is already present in the Container property. If not, this means this is the first UserControl to be added into this specific place holder. Therefore, it creates a new record empty record to hold the incoming UserControl(s).

After that, the specific place holder record is accessed using the ClientID of the place holder inside the Container's property. A new record for the UserControl is added that takes as the Key, the UserControl ClientID and as value the UserControl instance itself.

Finally, the ASP.NET Sesion variable is updated with the new/updated Container's property.

The final important method is the *LoadExistingControls* method. It is responsible for the actual rendering of the added UserControls into the ASPX page.

    /// <summary>
    /// This method takes as input the PlaceHolder
    /// where to load controls to. This allows you to
    /// load controls in any container placed on
    /// the page. It simply loops through the specific
    /// container, identified by the passed argument's
    /// ClientID, dictionary and adds every control found
    /// into the passed in place holder.
    /// </summary>
    protected void LoadExistingControls(PlaceHolder phContainer)
    {
        // If there are controls to load
        if (
            (this.Container != null) &&
            (this.Container.Count > 0)
            )
        {
            // If the container hasn't been intialized beofre
            if (!this.Container.ContainsKey(phContainer.ClientID))
                return;
           
            // Clear all previous controls
            phContainer.Controls.Clear();

            // Get every KeyValuePair, extract the UserControl from the Value
            // and add it to the container passed as parameter.
            foreach (KeyValuePair<string, UserControl> value in this.Container[phContainer.ClientID])
            {
                phContainer.Controls.Add(value.Value);
            }
        }
    }

It takes as input the place holder container to render. It starts by doing some validation to make sure there are records corresponding to the place holder entry in the Container's property. If this is true, it simply loops through all the records of that specific container and adds every instantiated UserControl into the passed-in place holder, which would be automatically rendered into the ASPX page.

These are basically the major methods and property required to implement a solution for adding controls dynamically.

Real Example
As an example, I have added a very simple UserControl called *MyControl.ascx* into the *~/App_Controls/* folder in the root of a website. The UserControl is very simple and it contains two labels and two textboxes representing the first name and last name.

The ASPX page should now inherit from the BasePage.cs class instead of the normal Page class. This gives it a way to use the utility methods we have developed above.

Inside the ASPX page, I have included some buttons and place holders that allow me to add new instances of the above UserControl dynamically. The diagram in Figure 1 below shows the ASPX page when it is running:

 

There are few things to mention and are very important inside the ASPX page.

You should reload all the controls that have been rendered dynamically on every request or postback!!

The best place inside the ASPX page to reload the existing controls is inside the Page_Init event handler as follows:

    protected void Page_Init(object sender, EventArgs e)
    {
        // ALWAYS Load existing controls
        LoadExistingControls(this.phContainer);
    }

You simply call the *LoadExistingControls* method on the BasePage and send it as parameter the first place holder I have included on the ASPX page. This will make sure the controls are reloaded!

When you want to add a new UserControl, simply press on the *Add New* button:

    /// <summary>
    /// Initializes a new UserControl and adds it to a container
    /// on the page.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void btnAddNew_Click(object sender, EventArgs e)
    {
        // Create a new control instance
        UserControl c = CreateControlInstance(
            MYCONTROL,
            MYCONTROL_ID);

        // Add the new control to the phContainer place holder
        AddControlToContainer(c, this.phContainer);

        // Load again the already added controls
        // to the specified container.
        LoadExistingControls(this.phContainer);
    }

The event handler for the button creates first of all a new instance of the UserControl by calling the *CreateControlInstance* on the BasePage class. Once a Usercontrol instance is created, it is passed to the *AddControlToContainer* method on the BasePage class. Finally, we reload all the controls again to reflect the changes of adding another UserControl. That's it!!

To make things more interesting. I have added an interface called *IMyControl.cs*:

public interface IMyControls
{
    /// <summary>
    /// A simple method to save the values in the control
    /// </summary>
    void Save();
}

It has a single method for the sake of this sample, however it can be improved and made more complex depending on your needs.

I forced all the UserControls I have in the application and that I want to load dynamically to implement this interface and give a concrete implementation for the *Save* method.

public partial class App_Controls_MyControl : System.Web.UI.UserControl, IMyControls

So now, when you click on the *Save Data* button on the ASPX page, it will simply loop through all the loaded controls in a specific container, extract an IMyControl interface implementation from the user control, and then call the Save method.

    /// <summary>
    /// Process the values inside each loaded UserControl
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void btnSave_Click(object sender, EventArgs e)
    {
        // If there are controls loaded inside the phContainer place holder
        // Every container is accessed by its ClientID
        if (
            (this.Container[this.phContainer.ClientID] != null) &&
            (this.Container[this.phContainer.ClientID].Count > 0)
            )
        {
            // Loop through all the KeyValuePairs that constitute
            // the elements of the specific container
            foreach (KeyValuePair<string, UserControl> value in this.Container[this.phContainer.ClientID])
            {
                // Case the value.Value which is a UserControl
                // into the Interface so that you can access
                // the Save method implementation on the UserControl
                ((IMyControls)value.Value).Save();
            }
        }
    }
 

As you can see, we are using the Container property to access the specific place holder. Once we have the specific container, we start looping over all the UserControls, get an IMyControl interface implementation and call on the Save method. You can add as many methods as you want based on your needs!

When you press the *Save Data* button you can see in Figure 2 that evert added control simply writes down its data to the screen. The ASPX page also shows how to handle not only one container but also 2 containers on the ASPX page and you can handle as much as you want!!

Summary
In this article, I have shown you one of the ways you can follow to implement adding controls dynamically into your ASPX pages. You can follow the code step by step as it was explained in the article and you can also download a working example.

Hope you enjoyed this article!

Regards

Tags:

Comments are closed