摘要:

     在本文中,我将向您演示在Asp.Net页面中动态加载控件的一种方法。

问题:

     假设您有一个Aspx页面,其包含一个名为"Add New"的Button,而您则想让用户每次点击Button,PostBack到Server,然后创建一个用户控件的实列,并最终想动态的添加到该页面的ControlCollections中。

     之所以我说用用户控件,是因为我们很容易把许多的控件组织到一个单独的控件中,添加的时候,我们只需把该用户控件添加到页面上就可以了。

设计解决方法:
     我所想到的解决该问题的方法是在Aspx页面上放一个PlaceHolder容器控件,该控件可以把所有的控件动态的添加到里面。另外,我们还设计一种"Page Controller"模式,让所有操作动态添加控件的方法都放到一个名为BasePage基类中,也就意味着,如果我们的一些Aspx页面想动态的添加一些控件,我们只需从这个BasePage类继承。

     最后,我们也必须考虑在一个Aspx页面上不仅仅包含一个PlaceHolder,反之,您可以把控件动态的添加到任何的地方

最终解决方法

首先在Vs 2008创建一个WebSite.

     创建完之后,添加一个BasePage类,然后把下面的代码添加到其中

在Asp.Net中动态加载控件_ide在Asp.Net中动态加载控件_用户控件_02Code

// 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}";

该SESSION_CONTAINERS_KEY将被用做Session 的值来保存,而PATH_TO_CONTROL则定义了您的程序所要载入用户控件的虚拟路径。

现在在添加数据字典属性

在Asp.Net中动态加载控件_ide在Asp.Net中动态加载控件_用户控件_02Code

/// <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;

        }

    }


如果您读上面属性的注释,您很容易就明白这段方法的作用。它仅仅只是把添加到页面上所有用户控件的每一个PlaceHolder放到一个字典中,页面上所有的PlaceHoder都有一个数据字典,并且每一个PlaceHolder都有一个用户控件集合的数据字典。

     在BasePage中我们还得需要一个创建用户控件实列的方法,我们命名为CreateControlInstance,以下是其实现:

在Asp.Net中动态加载控件_ide在Asp.Net中动态加载控件_用户控件_02Code

/// <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;

    }


这个方法很简单,我们只是把传过来的controlName,controlKey进行格式转换。对于controlName,我们把*~/App_Controls/{0}*其中的{0}参数, 替换成想 *~/App_Controls/MyControl.ascx"的形式,而把该Control的ID,用Guid值替换掉*MyControl_{0}*中的{0}参数。

另一个重要的方法是 *AddControlToContainer*. 其实现如下:

在Asp.Net中动态加载控件_ide在Asp.Net中动态加载控件_用户控件_02Code

/// <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;

    }


     这个方法非常重要,它接受两个参数,一个是您所添加的用户控件,一个是您想把该用户控件添加到传过来的指定的PlaceHolder.首先该方法会检查在Containers容器的列表中,连同添加到其中的Controls是否保存在Sesssion变量中,在这里您是否还记得我在上面提到的SESSION_CONTAINERS_KEY常量,它就是用作Session变量的retreival key。

     如果这个Session变量为空,也就意味着用户是首次添加一个用户控件,之后则创建一个Container的实列。

     该代码也检查在该Container容器中是否包含以PlaceHoler的ClientID为名称的字典,如果没有则说明用户控件是首次添加到指定的PlaceHolder控件中,为此,则添加一个新的空的的记录来保存将来要添加到该PlaceHolder中的UserControl或UserControls。

     随后在检查在该指定的PlaceHolder中是否包含以用户控件的ClientID为健的控件,如果没有,则添加到其PlaceHolder中。

     最后。Session[SESSION_CONTAINERS_KEY]被新的或者以更新的container所更新。

最后一个重要的方法是 *LoadExistingControls* ,它负责把所有的用户控件添加到Aspx页面中

在Asp.Net中动态加载控件_ide在Asp.Net中动态加载控件_用户控件_02Code

/// <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);

            }

        }

    }

该方法接收一个PlaceHolder作为其参数,并首先验证并确认在Container中是否包含相应的PlaceHolder,如果有,则循环所有指定Container中的所有记录,并把每一个用户控件添加到接收的PlaceHolder中。之后将会自动呈现在Aspx页面中。

     这就是动态添加控件基本主要的方法和属性的一种解决方案。

列子

     对于该列子,我添加了一个名为 *MyControl.ascx*用户控件,并把其放在站点的*~/App_Controls/* 文件夹下。该用户控件非常简单,仅仅包含输入姓名的两个文本框。

     而Aspx页面,则从我们刚才的BasePage类继承来替代标准的Page类,它也提供了我们额外的一些辅助方法。

     在Aspx中,包含一些的Button和我们想要动态添加控件容器的PlaceHolder。下面是首次运行的截图

在Asp.Net中动态加载控件_控件_11 在Aspx页面的.cs文件中还有一些非常重要的事要做

     首先,您应该在页面请求或者PostBack的时候,确保所有的Control是否添加到页面的ControlCollection中。

     而在Aspx页面中要实现该方法,最好的时机就是在页面欲呈现的时候。代码:

在Asp.Net中动态加载控件_ide在Asp.Net中动态加载控件_用户控件_02Code

 protected void Page_Init(object sender, EventArgs e)

    {

        // ALWAYS Load existing controls

        LoadExistingControls(this.phContainer);

    }

它就会调用BasePage中的 *LoadExistingControls* 方法,并把您所要动态添加控件的PlaceHolder作为参数传进去,确保所有的控件都载入到页面中。

当您想要添加一个新的用户控件,您只需点击一下名为 *Add New* 的Button 下面是该Button的代码

在Asp.Net中动态加载控件_ide在Asp.Net中动态加载控件_用户控件_02Code

/// <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);

    }

该Button首先调用了BasePage中的*CreateControlInstance* 来创建了一个新的用户控件的实列,一旦该实例创建,他就会把该实列和该实列的容器(phContainer)传递到BasePage中的*AddControlToContainer*方法,最后,我们在重新载入开始我们已经添加到该PlaceHolder中的控件。

     为了使该实列更有说服力,我添加了一个名为*IMyControl.cs*接口类:代码如下

在Asp.Net中动态加载控件_ide在Asp.Net中动态加载控件_用户控件_02Code

public interface IMyControls

{

    /// <summary>

    /// A simple method to save the values in the control

    /// </summary>

    void Save();

}

在其中,我们仅仅定义了一个方法,但是它却能对您的程序起到重要的作用。

我使所有的用户控件都继承这个接口,并实现它。

那好,现在当您点击页面上的*Save Data*按钮,它将会循环加载指定Container中的Controls,并调用继承自IMyControl接口的User Control的Save 方法. 代码如下:

在Asp.Net中动态加载控件_ide在Asp.Net中动态加载控件_用户控件_02Code

在Asp.Net中动态加载控件_用户控件_02在Asp.Net中动态加载控件_ide/**//// <summary>

在Asp.Net中动态加载控件_sed_22    /// Process the values inside each loaded UserControl

在Asp.Net中动态加载控件_sed_22    /// </summary>

在Asp.Net中动态加载控件_sed_22    /// <param name="sender"></param>

在Asp.Net中动态加载控件_ide_25    /// <param name="e"></param>

在Asp.Net中动态加载控件_动态添加_26    protected void btnSave_Click(object sender, EventArgs e)

在Asp.Net中动态加载控件_用户控件_02在Asp.Net中动态加载控件_ide    在Asp.Net中动态加载控件_sed_29{

在Asp.Net中动态加载控件_sed_22        // If there are controls loaded inside the phContainer place holder

在Asp.Net中动态加载控件_sed_22        // Every container is accessed by its ClientID

在Asp.Net中动态加载控件_sed_22        if (

在Asp.Net中动态加载控件_sed_22            (this.Container[this.phContainer.ClientID] != null) &&

在Asp.Net中动态加载控件_sed_22            (this.Container[this.phContainer.ClientID].Count > 0)

在Asp.Net中动态加载控件_sed_22            )

在Asp.Net中动态加载控件_ide_36在Asp.Net中动态加载控件_ide_37        在Asp.Net中动态加载控件_sed_29{

在Asp.Net中动态加载控件_sed_22            // Loop through all the KeyValuePairs that constitute

在Asp.Net中动态加载控件_sed_22            // the elements of the specific container

在Asp.Net中动态加载控件_sed_22            foreach (KeyValuePair<string, UserControl> value in this.Container[this.phContainer.ClientID])

在Asp.Net中动态加载控件_ide_36在Asp.Net中动态加载控件_ide_37            在Asp.Net中动态加载控件_sed_29{

在Asp.Net中动态加载控件_sed_22                // Case the value.Value which is a UserControl

在Asp.Net中动态加载控件_sed_22                // into the Interface so that you can access

在Asp.Net中动态加载控件_sed_22                // the Save method implementation on the UserControl

在Asp.Net中动态加载控件_sed_22                ((IMyControls)value.Value).Save();

在Asp.Net中动态加载控件_动态添加_49            }

在Asp.Net中动态加载控件_动态添加_49        }

在Asp.Net中动态加载控件_ide_25    }

正如你所看到的,我们使用Container属性访问我们指定的Place Holder,一旦访问成功,就开始循环其中的所有的UserControls,而后调用其中每一个实现IMyControl接口的Save 方法,您也可以实现您所需要的功能。

     当您点击*Save Data* 按钮,您就会看到如下的截图2,它只是把添加到页面上的UserControl的基本信息输出到页面上。

在Asp.Net中动态加载控件_动态添加_52     这个Aspx页面也演示了怎样处理不仅仅只有一个placeHolder也可能有2个或多个PlaceHolder,你可以按你需要任意添加多个。

总结

     在本文中,我只是介绍了动态向Aspx页面添加控件的一种方法。您可以跟随着本文的代码和说明进行阅读,或者下载本文中代码的源代码。