How To Develop/Adjust ASP.NET User Controls For Multiple Instances Support




When developing ASP.NET user controls you should keep in mind whether they need to support multiple instances feature or not. In other words, you should decide if more than one instance of your user control could be added on the same page. Why do you need to make up your mind on this? ...... wait and see.


Code Sample
You can download the code sample from here


Assume that you need to develop a user control which is simply a text box and a clear button. Once the clear button is clicked the text inside the text box should be cleared. Also, our user control should support multiple instances feature.

We will create the solution as in the image below.


Here we have two approaches to implement our user control; a BAD approach which will cause some issues as we will see and another GOOD approach which will work perfectly. So we will start with the bad approach to fully understand why we need the good one.


Bad Approach

TextWithClear.ascx:
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="TextWithClear.ascx.cs" Inherits="DevelopmentSimplyPut.MultiInstanceUserControl.Controls.TextWithClear" %>

<script type="text/javascript" src="../Scripts/jquery-1.11.0.min.js"></script>

<div style="border-color:red;border-width:thin;border-style:solid;width:20%;">
    <input id="txt_MyTextBox" name="txt_MyTextBox" type="text" value="" />
    <br />
    <input id="btn_Clear" type="button" value="Clear" onclick = "Clear();" />
</div>

<script type="text/javascript">
    function Clear() {
        $("#txt_MyTextBox").val("");
    }
</script>

Home.aspx:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Home.aspx.cs" Inherits="DevelopmentSimplyPut.MultiInstanceUserControl.Home" %>

<%@ Register Src="~/Controls/TextWithClear.ascx" TagPrefix="uc1" TagName="TextWithClear" %>


<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <uc1:TextWithClear runat="server" id="TextWithClear1" />
        <br />
        <uc1:TextWithClear runat="server" id="TextWithClear2" />
    </div>
    </form>
</body>
</html>


After running the application, we will get the image below.


As we can see both text boxes have the same id. Clicking the "Clear" button of the first instance will cause the first text box to be cleared as in the image below.


Unfortunately clicking the "Clear" button of the second instance also causes the first text box (not the second text box) to be cleared as in the image below.


This happened as both text boxes have the same id, so the javascript code selecting the text boxes by id will always return the first one only and then apply the clear action. That is why clicking the "Clear" button of both user control instances will always clear the first text box.

This is the time to try the good approach.


Good Approach

TextWithClear.ascx:
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="TextWithClear.ascx.cs" Inherits="DevelopmentSimplyPut.MultiInstanceUserControl.Controls.TextWithClear" %>

<script type="text/javascript" src="../Scripts/jquery-1.11.0.min.js"></script>

<div id="MainContainer" runat="server" style="border-color:red;border-width:thin;border-style:solid;width:20%;">
    <input id="txt_MyTextBox" name="txt_MyTextBox" type="text" value="" />
    <br />
    <input id="btn_Clear" type="button" value="Clear" onclick = "GetCurrentTextWithClearControlManager<%= this.ClientID %>().Clear();" />
</div>

<script type="text/javascript">
    function TextWithClearControlManager(_controlClientId) {
        this.ControlClientId = _controlClientId;
        this.GetMainContainerDomElement = function GetMainContainerDomElement() {
            return $("div[id^=" + this.ControlClientId + "][id$=MainContainer]").eq(0);
        };
        this.Clear = function Clear() {
            this.GetMainContainerDomElement().find("#txt_MyTextBox").val("");
        };
    }

    function GetCurrentTextWithClearControlManager<%= this.ClientID %>() {
        return new TextWithClearControlManager('<%= this.ClientID %>');
    }
</script>

Home.aspx:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Home.aspx.cs" Inherits="DevelopmentSimplyPut.MultiInstanceUserControl.Home" %>

<%@ Register Src="~/Controls/TextWithClear.ascx" TagPrefix="uc1" TagName="TextWithClear" %>


<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <uc1:TextWithClear runat="server" id="TextWithClear1" />
        <br />
        <uc1:TextWithClear runat="server" id="TextWithClear2" />
    </div>
    </form>
</body>
</html>


After running the application, we will get the image below.


As we can see both text boxes have the same id but each outer container div of each user control has its own unique id which is composed from the id of the user control and the static id which was given to the div. Clicking the "Clear" button of the first instance will cause the first text box to be cleared as in the image below.


Now clicking the "Clear" button of the second instance causes the second text box to be cleared as in the image below.


This is good news but what really happened here?

Steps
  1. Wrapped our user control into a main container DOM element or used an already existing one as in our case here
  2. Gave it an id which is "MainContainer" in our case
  3. Marked it as a server control by adding "runat="server""
  4. Added a javascript function which acts as a constructor for a manager object. This manager object is user control instance related. It controls and serves only the user control instance whose client id is passed to its constructor
  5. Inside this manager object we defined a function called "GetMainContainerDomElement" which is responsible for returning the main container DOM element of its corresponding user control. In our case here it returns the outer "MainContainer" div of corresponding user control
  6. Also inside this manager object we defined the clear function but this time we make use of the "GetMainContainerDomElement" function to focus on the current outer div of the user control corresponding to the current manager object. Then, we select the proper text box which is a child of this main div, not another div. This way we make sure that all actions will be applied on the corresponding user control instance without affecting any other instances
  7. Created the function whose name is composed of "GetCurrentTextWithClearControlManager" followed by the client id of the user control. This is to make sure that the function "GetCurrentTextWithClearControlManager" of all user control instances will not replace and overwrite each other as they are finally on the same page. This function is used to return the manager object of each user control instance to be used inside DOM elements tags. In our case we used it as follows "onclick = "GetCurrentTextWithClearControlManager<%= this.ClientID %>().Clear();""
Now each "Clear" button accesses its corresponding manager object and fires the right "Clear" function.


That's it. Hope this will help you someday.
Good luck.