admin管理员组

文章数量:1026989

I have 3 nested DataLists data being loaded from SQL query for each. DataList1_ItemCommand is working properly but DataList2_ItemCommand is not even firing.

protected void DataList1_ItemCommand(Object source, DataListCommandEventArgs e)
{
    if (e.CommandName == "Obiectiv")
    {
        if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
        {
            int idob = Convert.ToInt32(DataList1.DataKeys[e.Item.ItemIndex].ToString());

            DataList firstDataList = e.Item.FindControl("DataList2") as DataList;

            List<SqlParameter> prm = new List<SqlParameter>();
            prm.Add(new SqlParameter("@IdOB", idob));

            firstDataList.DataSource = GetDataTable("SELECT DISTINCT IdCL,Cladire FROM tblCladire WHERE IdOB= @IdOB;", prm);
            firstDataList.DataBind();

            HtmlControl theDivS = (HtmlControl)e.Item.FindControl("DivS");
            theDivS.Attributes["class"] = theDivS.Attributes["class"].Replace("hidden", "").Trim();
        }
    }
}

protected void DataList2_ItemCommand(Object source, DataListCommandEventArgs e)
{
    if (e.CommandName == "Cladire")
    {
        if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
        {
            DataList secondDataList = e.Item.FindControl("DataList2") as DataList;
            int idcl = Convert.ToInt32(secondDataList.DataKeys[e.Item.ItemIndex].ToString());
            
            DataList thirdDataList = e.Item.FindControl("DataList3") as DataList;
            List<SqlParameter> prm = new List<SqlParameter>
            {
                new SqlParameter("@IdCL", idcl)
            };

            thirdDataList.DataSource = GetDataTable("SELECT DISTINCT IdCA,Camera FROM tblCamera WHERE IdCL =@IdCL;", prm);
            thirdDataList.DataBind();

            HtmlControl theDivT = (HtmlControl)e.Item.FindControl("DivT");
            theDivT.Attributes["class"] = theDivT.Attributes["class"].Replace("hidden", "").Trim();
        }
    }
}
private static DataTable GetDataTable(string query, IEnumerable<SqlParameter> prms = null)
{
    string constr = ConfigurationManager.ConnectionStrings["ConnString"].ConnectionString;
    using (SqlConnection conn = new SqlConnection(constr))
    {
        using (SqlCommand cmd = new SqlCommand())
        {
            cmd.CommandText = query;

            using (SqlDataAdapter sda = new SqlDataAdapter())
            {
                cmd.Connection = conn;
                sda.SelectCommand = cmd;

                using (DataSet ds = new DataSet())
                {
                    DataTable dt = new DataTable();
                    if (prms is object)
                    {
                        foreach (var prm in prms)
                        {
                            cmd.Parameters.Add(prm);
                        }
                    }
                    sda.Fill(dt);
                    return dt;
                }
            }
        }
    }
}

First set of data is loaded on Page Load and it's working fine. Then, for each value in the list on click (command) load second set of data in DataList1 then on click (command) to load third set of data. What I have so far is first and second set of data. CommandName="Cladire" is not trigered.

<div id="DivP" runat="server">
    <div class="row no-gutters">
        <asp:DataList ID="DataList1" runat="server" Style="width: 100%" OnItemCommand="DataList1_ItemCommand" DataKeyField="IdOB">
            <ItemTemplate>
                <asp:TextBox ID="txtIDOB" class="hidden" runat="server" Text='<%#DataBinder.Eval(Container.DataItem,"IdOB")%>' />
                <asp:LinkButton ID="LinkButton10" runat="server" Text='<%#Eval("Obiectiv")%>' CommandName="Obiectiv" UseSubmitBehavior="false" Style="font-size: 11px;background-color:lightgreen" class="form-control form-control-sm" ClientIDMode="Static" />

                <div id="DivS" runat="server" class="hidden">
                    <hr style="border: 1px solid #800080" />
                    <asp:DataList ID="DataList2" runat="server" Style="width: 100%" OnItemCommand="DataList2_ItemCommand" DataKeyField="IdCL">
                        <ItemTemplate>
                            <asp:TextBox ID="txtIDCL" class="hidden" runat="server" Text='<%#DataBinder.Eval(Container.DataItem,"IdCL")%>' />
                            <asp:LinkButton ID="LinkButton11" runat="server" Text='<%#Eval("Cladire")%>' CommandName="Cladire" UseSubmitBehavior="false" Style="font-size: 11px;background-color:lightsalmon" class="form-control form-control-sm" ClientIDMode="Static" />

                            <div id="DivT" runat="server" class="hidden">
                                <hr style="border: 1px solid #800080" />
                                <asp:DataList ID="DataList3" runat="server" Style="width: 100%" OnItemCommand="DataList3_ItemCommand" DataKeyField="IdCA">
                                    <ItemTemplate>
                                        <asp:TextBox ID="txtIDCA" class="hidden" runat="server" Text='<%#DataBinder.Eval(Container.DataItem,"IdCA")%>' />
                                        <asp:LinkButton ID="LinkButton11" runat="server" Text='<%#Eval("Camera")%>' CommandName="Camera" UseSubmitBehavior="false" Style="font-size: 11px;background-color:lightblue" class="form-control form-control-sm" />
                                    </ItemTemplate>
                                </asp:DataList>
                                <hr style="border: 1px solid #800080" />
                            </div>
                        </ItemTemplate>
                    </asp:DataList>
                    <hr style="border: 1px solid #800080" />
                </div>
            </ItemTemplate>
        </asp:DataList>
    </div>
</div>

I have 3 nested DataLists data being loaded from SQL query for each. DataList1_ItemCommand is working properly but DataList2_ItemCommand is not even firing.

protected void DataList1_ItemCommand(Object source, DataListCommandEventArgs e)
{
    if (e.CommandName == "Obiectiv")
    {
        if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
        {
            int idob = Convert.ToInt32(DataList1.DataKeys[e.Item.ItemIndex].ToString());

            DataList firstDataList = e.Item.FindControl("DataList2") as DataList;

            List<SqlParameter> prm = new List<SqlParameter>();
            prm.Add(new SqlParameter("@IdOB", idob));

            firstDataList.DataSource = GetDataTable("SELECT DISTINCT IdCL,Cladire FROM tblCladire WHERE IdOB= @IdOB;", prm);
            firstDataList.DataBind();

            HtmlControl theDivS = (HtmlControl)e.Item.FindControl("DivS");
            theDivS.Attributes["class"] = theDivS.Attributes["class"].Replace("hidden", "").Trim();
        }
    }
}

protected void DataList2_ItemCommand(Object source, DataListCommandEventArgs e)
{
    if (e.CommandName == "Cladire")
    {
        if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
        {
            DataList secondDataList = e.Item.FindControl("DataList2") as DataList;
            int idcl = Convert.ToInt32(secondDataList.DataKeys[e.Item.ItemIndex].ToString());
            
            DataList thirdDataList = e.Item.FindControl("DataList3") as DataList;
            List<SqlParameter> prm = new List<SqlParameter>
            {
                new SqlParameter("@IdCL", idcl)
            };

            thirdDataList.DataSource = GetDataTable("SELECT DISTINCT IdCA,Camera FROM tblCamera WHERE IdCL =@IdCL;", prm);
            thirdDataList.DataBind();

            HtmlControl theDivT = (HtmlControl)e.Item.FindControl("DivT");
            theDivT.Attributes["class"] = theDivT.Attributes["class"].Replace("hidden", "").Trim();
        }
    }
}
private static DataTable GetDataTable(string query, IEnumerable<SqlParameter> prms = null)
{
    string constr = ConfigurationManager.ConnectionStrings["ConnString"].ConnectionString;
    using (SqlConnection conn = new SqlConnection(constr))
    {
        using (SqlCommand cmd = new SqlCommand())
        {
            cmd.CommandText = query;

            using (SqlDataAdapter sda = new SqlDataAdapter())
            {
                cmd.Connection = conn;
                sda.SelectCommand = cmd;

                using (DataSet ds = new DataSet())
                {
                    DataTable dt = new DataTable();
                    if (prms is object)
                    {
                        foreach (var prm in prms)
                        {
                            cmd.Parameters.Add(prm);
                        }
                    }
                    sda.Fill(dt);
                    return dt;
                }
            }
        }
    }
}

First set of data is loaded on Page Load and it's working fine. Then, for each value in the list on click (command) load second set of data in DataList1 then on click (command) to load third set of data. What I have so far is first and second set of data. CommandName="Cladire" is not trigered.

<div id="DivP" runat="server">
    <div class="row no-gutters">
        <asp:DataList ID="DataList1" runat="server" Style="width: 100%" OnItemCommand="DataList1_ItemCommand" DataKeyField="IdOB">
            <ItemTemplate>
                <asp:TextBox ID="txtIDOB" class="hidden" runat="server" Text='<%#DataBinder.Eval(Container.DataItem,"IdOB")%>' />
                <asp:LinkButton ID="LinkButton10" runat="server" Text='<%#Eval("Obiectiv")%>' CommandName="Obiectiv" UseSubmitBehavior="false" Style="font-size: 11px;background-color:lightgreen" class="form-control form-control-sm" ClientIDMode="Static" />

                <div id="DivS" runat="server" class="hidden">
                    <hr style="border: 1px solid #800080" />
                    <asp:DataList ID="DataList2" runat="server" Style="width: 100%" OnItemCommand="DataList2_ItemCommand" DataKeyField="IdCL">
                        <ItemTemplate>
                            <asp:TextBox ID="txtIDCL" class="hidden" runat="server" Text='<%#DataBinder.Eval(Container.DataItem,"IdCL")%>' />
                            <asp:LinkButton ID="LinkButton11" runat="server" Text='<%#Eval("Cladire")%>' CommandName="Cladire" UseSubmitBehavior="false" Style="font-size: 11px;background-color:lightsalmon" class="form-control form-control-sm" ClientIDMode="Static" />

                            <div id="DivT" runat="server" class="hidden">
                                <hr style="border: 1px solid #800080" />
                                <asp:DataList ID="DataList3" runat="server" Style="width: 100%" OnItemCommand="DataList3_ItemCommand" DataKeyField="IdCA">
                                    <ItemTemplate>
                                        <asp:TextBox ID="txtIDCA" class="hidden" runat="server" Text='<%#DataBinder.Eval(Container.DataItem,"IdCA")%>' />
                                        <asp:LinkButton ID="LinkButton11" runat="server" Text='<%#Eval("Camera")%>' CommandName="Camera" UseSubmitBehavior="false" Style="font-size: 11px;background-color:lightblue" class="form-control form-control-sm" />
                                    </ItemTemplate>
                                </asp:DataList>
                                <hr style="border: 1px solid #800080" />
                            </div>
                        </ItemTemplate>
                    </asp:DataList>
                    <hr style="border: 1px solid #800080" />
                </div>
            </ItemTemplate>
        </asp:DataList>
    </div>
</div>
Share Improve this question edited Nov 16, 2024 at 18:49 NMV asked Nov 16, 2024 at 15:43 NMVNMV 154 bronze badges 1
  • Yikes, this has SQL injection issues. It's practically begging to get hacked! Image what would happen if you put ';DROP TABLE tblCamera;-- into your txtIDCL textbox? – Joel Coehoorn Commented Nov 16, 2024 at 16:21
Add a comment  | 

3 Answers 3

Reset to default 2

Itemcommand is a less then ideal event to use.

Just nest the 3 tables, and place a good old fashioned regular button in each row. Nothing more really required.

Also, datalist is perhaps the "oldest" of the 3 grid like controls. I recommend using GridView, or even better ListView.

Let's nest 3 tables. A city list, then for each city, hotels, and then for each hotel we can show the people booked.

As noted, attempts to nest a datalist is a real challenge for layout, since each nested datalist will be forced into one column of the parent table.

Using a ListView hence works better.

So, outer top most ListView.

        <asp:ListView ID="LVCity" runat="server" >
            <ItemTemplate>
                <tr>
                    <td>
                        <asp:Button ID="cmdCity" runat="server" 
                            Text="+"
                            OnClick="cmdCity_Click"/>
                    </td>
                    <td>
                        <asp:Label ID="lblCity" runat="server" Text='<%# Eval("City") %>' />
                    </td>
                    <tr>
                        <td colspan="2">
                            2nd list view goes here
                        </td>
                    </tr>
                </tr>
            </ItemTemplate>
            <LayoutTemplate>
                <table id="itemPlaceholderContainer" runat="server"
                    class="table table-bordered table-hover">
                    <tr runat="server" style="">
                        <th runat="server" style="width:80px"></th>
                        <th runat="server">City</th>
                    </tr>
                    <tr id="itemPlaceholder" runat="server">
                    </tr>
                </table>
            </LayoutTemplate>
        </asp:ListView>

Now, while the ListView is a "somewhat" more layout, it still follows a HTML table layout. And since each asp control does not require "template" fields, then it is a bit of give and take - but as the markup becomes more complex, then the ListView starts to win due to better layout, and often even less markup.

Note in above where the place holder is for the nested (second table).

Code behind to load above is thus this:

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
            LoadData();
    }


    void LoadData()
    {
        string strSQL = "SELECT City from City ORDER BY City";

        LVCity.DataSource = General.MyRst(strSQL);
        LVCity.DataBind();
    }

And the button click for the one row is thus this:

    protected void cmdCity_Click(object sender, EventArgs e)
    {
        Button cmd = (Button)sender;
        ListViewItem CityRow = (ListViewItem)cmd.NamingContainer;

        Label lblCity = (Label)CityRow.FindControl("lblCity");
        ListView LVHotels = (ListView)CityRow.FindControl("LVHotels");

        string strSQL = "SELECT * FROM tblHotelsA WHERE CITY = @City";
        SqlCommand cmdSQL = new SqlCommand(strSQL);
        cmdSQL.Parameters.Add("@City", SqlDbType.NVarChar).Value = lblCity.Text;

        LVHotels.DataSource = General.MyRstP(cmdSQL);
        LVHotels.DataBind();    

    }

I actually built each ListView seperate, and them "moved" each ListView into the child ListView. So, including above markup, then we have this final markup of 3 nested ListViews

        <asp:ListView ID="LVCity" runat="server">
            <ItemTemplate>
                <tr>
                    <td>
                        <asp:Button ID="cmdCity" runat="server"
                            Text="+"
                            OnClick="cmdCity_Click" />
                    </td>
                    <td>
                        <asp:Label ID="lblCity" runat="server" Text='<%# Eval("City") %>' />
                    </td>
                    <tr>
                        <td colspan="2">
            <asp:ListView ID="LVHotels" runat="server" DataKeyNames="ID">
                <ItemTemplate>
                    <tr>
                        <td style="text-align: center; vertical-align: middle">
                            <asp:Button ID="cmdViewBooked" runat="server"
                                Text="+" class="btn"
                                OnClick="cmdViewBooked_Click" />
                        </td>
                        <td>
                            <asp:Label ID="HotelNameLabel" runat="server" Text='<%# Eval("HotelName") %>' /></td>
                        <td>
                            <asp:Label ID="CityLabel" runat="server" Text='<%# Eval("City") %>' /></td>
                        <td style="width: 350px">
                            <asp:Label ID="DescriptionLabel" runat="server" Text='<%# Eval("Description") %>' />
                        </td>
                        <tr>
                        <td colspan="4">
         <asp:ListView ID="LVBooked" runat="server" DataKeyNames="ID">
            <ItemTemplate>
                <tr>
                    <td><asp:Label ID="FirstName" runat="server" Text='<%# Eval("FirstName")%>' /> </td>
                    <td><asp:Label ID="LastName" runat="server" Text='<%# Eval("LastName") %>' /></td>
                    <td><asp:Label ID="City" runat="server" Text='<%# Eval("City") %>' /></td>
                </tr>
            </ItemTemplate>
            <LayoutTemplate>
                <table id="itemPlaceholderContainer" runat="server"
                    class="table table-bordered table-hover"
                     style="margin-left:125px">
                    <tr runat="server" style="">
                        <th runat="server">FirstName</th>
                        <th runat="server">LastName</th>
                        <th runat="server">City</th>
                    </tr>
                    <tr id="itemPlaceholder" runat="server">
                    </tr>
                </table>
            </LayoutTemplate>
        </asp:ListView>
                            </td>
                        </tr>
                    </tr>
         </ItemTemplate>
        <LayoutTemplate>
            <table id="itemPlaceholderContainer" runat="server"
                class="table table-bordered table-hover">
                <tr runat="server" style="">
                    <th runat="server"></th>
                    <th runat="server">HotelName</th>
                    <th runat="server">City</th>
                    <th runat="server">Description</th>
                </tr>
                <tr id="itemPlaceholder" runat="server">
                </tr>
            </table>
        </LayoutTemplate>
    </asp:ListView>
                        </td>
                    </tr>
                </tr>
            </ItemTemplate>
            <LayoutTemplate>
                <table id="itemPlaceholderContainer" runat="server"
                    class="table table-bordered table-hover">
                    <tr runat="server" style="">
                        <th runat="server" style="width:80px"></th>
                        <th runat="server">City</th>
                    </tr>
                    <tr id="itemPlaceholder" runat="server">
                    </tr>
                </table>
            </LayoutTemplate>
     </asp:ListView>

And now the full code behind listing is this:

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
            LoadData();
    }

    void LoadData()
    {
        string strSQL = "SELECT City from City ORDER BY City";

        LVCity.DataSource = General.MyRst(strSQL);
        LVCity.DataBind();
    }

    protected void cmdCity_Click(object sender, EventArgs e)
    {
        Button cmd = (Button)sender;
        ListViewItem CityRow = (ListViewItem)cmd.NamingContainer;

        Label lblCity = (Label)CityRow.FindControl("lblCity");
        ListView LVHotels = (ListView)CityRow.FindControl("LVHotels");

        string strSQL = "SELECT * FROM tblHotelsA WHERE CITY = @City";
        SqlCommand cmdSQL = new SqlCommand(strSQL);
        cmdSQL.Parameters.Add("@City", SqlDbType.NVarChar).Value = lblCity.Text;

        LVHotels.DataSource = General.MyRstP(cmdSQL);
        LVHotels.DataBind();    

    }

    protected void cmdViewBooked_Click(object sender, EventArgs e)
    {
        Button cmd = (Button)sender;
        ListViewItem HotelRow = (ListViewItem)cmd.NamingContainer;

        // get hidden Hotel PK id
        ListView LVHotels = (ListView)HotelRow.NamingContainer;
        int HotelID = (int)LVHotels.DataKeys[HotelRow.DisplayIndex]["ID"];

        ListView LVBooked = (ListView)HotelRow.FindControl("LVBooked");


        string strSQL =
            "SELECT * FROM People WHERE Hotel_ID = @ID";
        SqlCommand cmdSQL = new SqlCommand(strSQL);
        cmdSQL.Parameters.Add("@ID", SqlDbType.Int).Value = HotelID;

        DataTable dt = General.MyRstP(cmdSQL);

        LVBooked.DataSource = General.MyRstP(cmdSQL);
        LVBooked.DataBind();

    }

And the result is this this:

Keep in mind the SAME approach can be used with a datalist (use of the NamingContainer to get the row click), and hence we don't bother nor care or even try to deal with the built in event model of the datalist (or GridView, or ListView). So, even if you don't adopt ListView, a standard button click event, and use of naming container to pick up the current row click can still be adopted.

I could also with ease have the button click "collapse/hide" the expanded child table.

For completeness, one fast becomes tired of writing code to return a table based on given SQL or SQL command object, so in my general (global) helper class code, I have the following two routines used in above.

    public static DataTable MyRst(string strSQL)
    {
        DataTable rstData = new DataTable();
        using (SqlConnection conn = new SqlConnection(Properties.Settings.Default.TEST4))
        {
            using (SqlCommand cmdSQL = new SqlCommand(strSQL, conn))
            {
                cmdSQL.Connection.Open();
                rstData.Load(cmdSQL.ExecuteReader());
            }
        }
        return rstData;
    }


    public static DataTable MyRstP(SqlCommand cmdSQL)
    {
        DataTable rstData = new DataTable();
        using (SqlConnection conn = new SqlConnection(Properties.Settings.Default.TEST4))
        {
            using (cmdSQL)
            {
                cmdSQL.Connection = conn;
                conn.Open();
                rstData.Load(cmdSQL.ExecuteReader());
            }
        }
        return rstData;
    }

Edit: Adding a show and hide (toggle) of the expanded child tables

To allow a expand, and hide (show + collapse), it is a simple matter to use the .visible property of each child table (ListView).

Hence, for the two child tables, we change the markup to this:

            <asp:ListView ID="LVHotels" runat="server" DataKeyNames="ID"
                visible="false">

and

         <asp:ListView ID="LVBooked" runat="server" DataKeyNames="ID"
             Visible="false" >

Now, in our code behind for the above 2 child tables, we simply add some simple toggle code, and bonus points is we change the button "+" to a "-" to indicate hitting button again will collapse (hide) the child table.

Hence, our two events now become this:

    protected void cmdCity_Click(object sender, EventArgs e)
    {
        Button cmd = (Button)sender;
        ListViewItem CityRow = (ListViewItem)cmd.NamingContainer;

        Label lblCity = (Label)CityRow.FindControl("lblCity");
        ListView LVHotels = (ListView)CityRow.FindControl("LVHotels");

        // if LVHotels is visible, then hide it, and exit

        if (LVHotels.Visible)
        {
            cmd.Text = "+";
            LVHotels.Visible = false;
            return;
        }

        // get here, and table is not visible, show it
        cmd.Text = "-";
        LVHotels.Visible = true;

        string strSQL = "SELECT * FROM tblHotelsA WHERE CITY = @City";
        SqlCommand cmdSQL = new SqlCommand(strSQL);
        cmdSQL.Parameters.Add("@City", SqlDbType.NVarChar).Value = lblCity.Text;

        LVHotels.DataSource = General.MyRstP(cmdSQL);
        LVHotels.DataBind();    

    }

    protected void cmdViewBooked_Click(object sender, EventArgs e)
    {
        Button cmd = (Button)sender;
        ListViewItem HotelRow = (ListViewItem)cmd.NamingContainer;

        // get hidden Hotel PK id
        ListView LVHotels = (ListView)HotelRow.NamingContainer;
        int HotelID = (int)LVHotels.DataKeys[HotelRow.DisplayIndex]["ID"];

        ListView LVBooked = (ListView)HotelRow.FindControl("LVBooked");


        if (LVBooked.Visible)
        {
            cmd.Text = "+";
            LVBooked.Visible = false;
            return;
        }

        // get here, and table is not visible, show it
        LVBooked.Visible=true;
        cmd.Text = "-"; 

        string strSQL =
            "SELECT * FROM People WHERE Hotel_ID = @ID";
        SqlCommand cmdSQL = new SqlCommand(strSQL);
        cmdSQL.Parameters.Add("@ID", SqlDbType.Int).Value = HotelID;

        DataTable dt = General.MyRstP(cmdSQL);

        LVBooked.DataSource = General.MyRstP(cmdSQL);
        LVBooked.DataBind();

    }

And now the result is to expand, we see the button change from a "+" to a "-" and the expanded child table is displayed.

And of course collapsing (hiding) works equally well now:

Separate from the bad TextBox + SQL injection issue, there is a problem with how this code uses the ASP.Net Webforms Page Lifecycle.

Every button click is a complete rebuild of the entire page from scratch, including the base grid. If not done correctly, at the point in the page life cycle where ASP.Net should be registering the click event to fire, the grid elements will not exist yet. If that happens, the event won't fire.

Personally, I think they should make a new exception type specifically for this issue that is thrown when it's time to wire up an event to a control that doesn't exist, with an exception message to explain what's going on.

To resolve it, you have two options:

  1. Make sure your parent grid is re-populated from the database before the events are registered. This means building the grid in the PreInit event instead of the Load event.

    If the main grid itself loads or updates in response to an event — perhaps something like a textbox with a search filter — that also means ensuring that state is saved in a mechanism available at PreInit time.

  2. Refactor to use an UpdatePanel managed from javascript, instead of a nested grid. This can dramatically improve both real and user-perceived performance, because with an UpdatePanel the page in the browser does not unload (no DOM freeze/rebuild) and you only need to query the database for the child data; you don't need to rebuild the rest of the page on the server.

Either way, it's a significant change from the original post. This scenario is one of the reasons webforms has fallen out of favor.

How are you confirming the command is not triggered? Because, while that is possible, there is a separate issue in the code that will prevent you from seeing data even if the code does run.

In the second method, the idcl variable is a Textbox rather than a string. The result is the SQL command will always look like this, no matter what value someone enters in the textbox:

SELECT DISTINCT IdCA,Camera FROM tblCamera WHERE IdCL ='System.Web.UI.WebControls.WebControl.TextBox'

Because it will implicitly call ToString() on the TextBox object, and most objects just output the type name if not explicitly changed to do something else. I doubt there's any data in your database matching that string value.


Of course, the quick/naïve solution is using the control's .Text property to get the desired string value for the SQL statement, instead of the control itself.

But that would be really bad.

The real solution is understanding the GetDataTable() method is fundamentally flawed in a way that is forcing you to write seriously unsafe code. You need to look into using parameterized queries, so the code will look something more like this:

List<SqlParameter> parameters = new() {
    new SqlParameter("@IdCL", SqlDbType.NVarChar, 20)
};
parameters[0].Value = idcl.Text;
secondDataList.DataSource = GetDataTable("SELECT DISTINCT IdCA,Camera FROM tblCamera WHERE IdCL = @IdCL;", parameters );

Here we see I'm imagining a revision to the GetDataTable() method that allows to pass an IEnumerable<SqlParameter> as an additional argument. This isn't the only solution or way to accomplish it, but the important thing is you should NEVER use string concatenation to substitute data into an SQL statement!!

When you have this part working, you need to do same thing for the idob variable in the first method. Fixing this throughout your application should be your top priority.


Again: it is also possible the code is indeed not even running, and I have another idea about why that may be (related to the page life cycle and needing to re-build the outer control in the Pre-Init phase, so events for the nested inner controls can be restored at the right time). But I won't get into that more until we've looked at this other issue, which is a HUGE gaping security problem.

I have 3 nested DataLists data being loaded from SQL query for each. DataList1_ItemCommand is working properly but DataList2_ItemCommand is not even firing.

protected void DataList1_ItemCommand(Object source, DataListCommandEventArgs e)
{
    if (e.CommandName == "Obiectiv")
    {
        if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
        {
            int idob = Convert.ToInt32(DataList1.DataKeys[e.Item.ItemIndex].ToString());

            DataList firstDataList = e.Item.FindControl("DataList2") as DataList;

            List<SqlParameter> prm = new List<SqlParameter>();
            prm.Add(new SqlParameter("@IdOB", idob));

            firstDataList.DataSource = GetDataTable("SELECT DISTINCT IdCL,Cladire FROM tblCladire WHERE IdOB= @IdOB;", prm);
            firstDataList.DataBind();

            HtmlControl theDivS = (HtmlControl)e.Item.FindControl("DivS");
            theDivS.Attributes["class"] = theDivS.Attributes["class"].Replace("hidden", "").Trim();
        }
    }
}

protected void DataList2_ItemCommand(Object source, DataListCommandEventArgs e)
{
    if (e.CommandName == "Cladire")
    {
        if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
        {
            DataList secondDataList = e.Item.FindControl("DataList2") as DataList;
            int idcl = Convert.ToInt32(secondDataList.DataKeys[e.Item.ItemIndex].ToString());
            
            DataList thirdDataList = e.Item.FindControl("DataList3") as DataList;
            List<SqlParameter> prm = new List<SqlParameter>
            {
                new SqlParameter("@IdCL", idcl)
            };

            thirdDataList.DataSource = GetDataTable("SELECT DISTINCT IdCA,Camera FROM tblCamera WHERE IdCL =@IdCL;", prm);
            thirdDataList.DataBind();

            HtmlControl theDivT = (HtmlControl)e.Item.FindControl("DivT");
            theDivT.Attributes["class"] = theDivT.Attributes["class"].Replace("hidden", "").Trim();
        }
    }
}
private static DataTable GetDataTable(string query, IEnumerable<SqlParameter> prms = null)
{
    string constr = ConfigurationManager.ConnectionStrings["ConnString"].ConnectionString;
    using (SqlConnection conn = new SqlConnection(constr))
    {
        using (SqlCommand cmd = new SqlCommand())
        {
            cmd.CommandText = query;

            using (SqlDataAdapter sda = new SqlDataAdapter())
            {
                cmd.Connection = conn;
                sda.SelectCommand = cmd;

                using (DataSet ds = new DataSet())
                {
                    DataTable dt = new DataTable();
                    if (prms is object)
                    {
                        foreach (var prm in prms)
                        {
                            cmd.Parameters.Add(prm);
                        }
                    }
                    sda.Fill(dt);
                    return dt;
                }
            }
        }
    }
}

First set of data is loaded on Page Load and it's working fine. Then, for each value in the list on click (command) load second set of data in DataList1 then on click (command) to load third set of data. What I have so far is first and second set of data. CommandName="Cladire" is not trigered.

<div id="DivP" runat="server">
    <div class="row no-gutters">
        <asp:DataList ID="DataList1" runat="server" Style="width: 100%" OnItemCommand="DataList1_ItemCommand" DataKeyField="IdOB">
            <ItemTemplate>
                <asp:TextBox ID="txtIDOB" class="hidden" runat="server" Text='<%#DataBinder.Eval(Container.DataItem,"IdOB")%>' />
                <asp:LinkButton ID="LinkButton10" runat="server" Text='<%#Eval("Obiectiv")%>' CommandName="Obiectiv" UseSubmitBehavior="false" Style="font-size: 11px;background-color:lightgreen" class="form-control form-control-sm" ClientIDMode="Static" />

                <div id="DivS" runat="server" class="hidden">
                    <hr style="border: 1px solid #800080" />
                    <asp:DataList ID="DataList2" runat="server" Style="width: 100%" OnItemCommand="DataList2_ItemCommand" DataKeyField="IdCL">
                        <ItemTemplate>
                            <asp:TextBox ID="txtIDCL" class="hidden" runat="server" Text='<%#DataBinder.Eval(Container.DataItem,"IdCL")%>' />
                            <asp:LinkButton ID="LinkButton11" runat="server" Text='<%#Eval("Cladire")%>' CommandName="Cladire" UseSubmitBehavior="false" Style="font-size: 11px;background-color:lightsalmon" class="form-control form-control-sm" ClientIDMode="Static" />

                            <div id="DivT" runat="server" class="hidden">
                                <hr style="border: 1px solid #800080" />
                                <asp:DataList ID="DataList3" runat="server" Style="width: 100%" OnItemCommand="DataList3_ItemCommand" DataKeyField="IdCA">
                                    <ItemTemplate>
                                        <asp:TextBox ID="txtIDCA" class="hidden" runat="server" Text='<%#DataBinder.Eval(Container.DataItem,"IdCA")%>' />
                                        <asp:LinkButton ID="LinkButton11" runat="server" Text='<%#Eval("Camera")%>' CommandName="Camera" UseSubmitBehavior="false" Style="font-size: 11px;background-color:lightblue" class="form-control form-control-sm" />
                                    </ItemTemplate>
                                </asp:DataList>
                                <hr style="border: 1px solid #800080" />
                            </div>
                        </ItemTemplate>
                    </asp:DataList>
                    <hr style="border: 1px solid #800080" />
                </div>
            </ItemTemplate>
        </asp:DataList>
    </div>
</div>

I have 3 nested DataLists data being loaded from SQL query for each. DataList1_ItemCommand is working properly but DataList2_ItemCommand is not even firing.

protected void DataList1_ItemCommand(Object source, DataListCommandEventArgs e)
{
    if (e.CommandName == "Obiectiv")
    {
        if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
        {
            int idob = Convert.ToInt32(DataList1.DataKeys[e.Item.ItemIndex].ToString());

            DataList firstDataList = e.Item.FindControl("DataList2") as DataList;

            List<SqlParameter> prm = new List<SqlParameter>();
            prm.Add(new SqlParameter("@IdOB", idob));

            firstDataList.DataSource = GetDataTable("SELECT DISTINCT IdCL,Cladire FROM tblCladire WHERE IdOB= @IdOB;", prm);
            firstDataList.DataBind();

            HtmlControl theDivS = (HtmlControl)e.Item.FindControl("DivS");
            theDivS.Attributes["class"] = theDivS.Attributes["class"].Replace("hidden", "").Trim();
        }
    }
}

protected void DataList2_ItemCommand(Object source, DataListCommandEventArgs e)
{
    if (e.CommandName == "Cladire")
    {
        if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
        {
            DataList secondDataList = e.Item.FindControl("DataList2") as DataList;
            int idcl = Convert.ToInt32(secondDataList.DataKeys[e.Item.ItemIndex].ToString());
            
            DataList thirdDataList = e.Item.FindControl("DataList3") as DataList;
            List<SqlParameter> prm = new List<SqlParameter>
            {
                new SqlParameter("@IdCL", idcl)
            };

            thirdDataList.DataSource = GetDataTable("SELECT DISTINCT IdCA,Camera FROM tblCamera WHERE IdCL =@IdCL;", prm);
            thirdDataList.DataBind();

            HtmlControl theDivT = (HtmlControl)e.Item.FindControl("DivT");
            theDivT.Attributes["class"] = theDivT.Attributes["class"].Replace("hidden", "").Trim();
        }
    }
}
private static DataTable GetDataTable(string query, IEnumerable<SqlParameter> prms = null)
{
    string constr = ConfigurationManager.ConnectionStrings["ConnString"].ConnectionString;
    using (SqlConnection conn = new SqlConnection(constr))
    {
        using (SqlCommand cmd = new SqlCommand())
        {
            cmd.CommandText = query;

            using (SqlDataAdapter sda = new SqlDataAdapter())
            {
                cmd.Connection = conn;
                sda.SelectCommand = cmd;

                using (DataSet ds = new DataSet())
                {
                    DataTable dt = new DataTable();
                    if (prms is object)
                    {
                        foreach (var prm in prms)
                        {
                            cmd.Parameters.Add(prm);
                        }
                    }
                    sda.Fill(dt);
                    return dt;
                }
            }
        }
    }
}

First set of data is loaded on Page Load and it's working fine. Then, for each value in the list on click (command) load second set of data in DataList1 then on click (command) to load third set of data. What I have so far is first and second set of data. CommandName="Cladire" is not trigered.

<div id="DivP" runat="server">
    <div class="row no-gutters">
        <asp:DataList ID="DataList1" runat="server" Style="width: 100%" OnItemCommand="DataList1_ItemCommand" DataKeyField="IdOB">
            <ItemTemplate>
                <asp:TextBox ID="txtIDOB" class="hidden" runat="server" Text='<%#DataBinder.Eval(Container.DataItem,"IdOB")%>' />
                <asp:LinkButton ID="LinkButton10" runat="server" Text='<%#Eval("Obiectiv")%>' CommandName="Obiectiv" UseSubmitBehavior="false" Style="font-size: 11px;background-color:lightgreen" class="form-control form-control-sm" ClientIDMode="Static" />

                <div id="DivS" runat="server" class="hidden">
                    <hr style="border: 1px solid #800080" />
                    <asp:DataList ID="DataList2" runat="server" Style="width: 100%" OnItemCommand="DataList2_ItemCommand" DataKeyField="IdCL">
                        <ItemTemplate>
                            <asp:TextBox ID="txtIDCL" class="hidden" runat="server" Text='<%#DataBinder.Eval(Container.DataItem,"IdCL")%>' />
                            <asp:LinkButton ID="LinkButton11" runat="server" Text='<%#Eval("Cladire")%>' CommandName="Cladire" UseSubmitBehavior="false" Style="font-size: 11px;background-color:lightsalmon" class="form-control form-control-sm" ClientIDMode="Static" />

                            <div id="DivT" runat="server" class="hidden">
                                <hr style="border: 1px solid #800080" />
                                <asp:DataList ID="DataList3" runat="server" Style="width: 100%" OnItemCommand="DataList3_ItemCommand" DataKeyField="IdCA">
                                    <ItemTemplate>
                                        <asp:TextBox ID="txtIDCA" class="hidden" runat="server" Text='<%#DataBinder.Eval(Container.DataItem,"IdCA")%>' />
                                        <asp:LinkButton ID="LinkButton11" runat="server" Text='<%#Eval("Camera")%>' CommandName="Camera" UseSubmitBehavior="false" Style="font-size: 11px;background-color:lightblue" class="form-control form-control-sm" />
                                    </ItemTemplate>
                                </asp:DataList>
                                <hr style="border: 1px solid #800080" />
                            </div>
                        </ItemTemplate>
                    </asp:DataList>
                    <hr style="border: 1px solid #800080" />
                </div>
            </ItemTemplate>
        </asp:DataList>
    </div>
</div>
Share Improve this question edited Nov 16, 2024 at 18:49 NMV asked Nov 16, 2024 at 15:43 NMVNMV 154 bronze badges 1
  • Yikes, this has SQL injection issues. It's practically begging to get hacked! Image what would happen if you put ';DROP TABLE tblCamera;-- into your txtIDCL textbox? – Joel Coehoorn Commented Nov 16, 2024 at 16:21
Add a comment  | 

3 Answers 3

Reset to default 2

Itemcommand is a less then ideal event to use.

Just nest the 3 tables, and place a good old fashioned regular button in each row. Nothing more really required.

Also, datalist is perhaps the "oldest" of the 3 grid like controls. I recommend using GridView, or even better ListView.

Let's nest 3 tables. A city list, then for each city, hotels, and then for each hotel we can show the people booked.

As noted, attempts to nest a datalist is a real challenge for layout, since each nested datalist will be forced into one column of the parent table.

Using a ListView hence works better.

So, outer top most ListView.

        <asp:ListView ID="LVCity" runat="server" >
            <ItemTemplate>
                <tr>
                    <td>
                        <asp:Button ID="cmdCity" runat="server" 
                            Text="+"
                            OnClick="cmdCity_Click"/>
                    </td>
                    <td>
                        <asp:Label ID="lblCity" runat="server" Text='<%# Eval("City") %>' />
                    </td>
                    <tr>
                        <td colspan="2">
                            2nd list view goes here
                        </td>
                    </tr>
                </tr>
            </ItemTemplate>
            <LayoutTemplate>
                <table id="itemPlaceholderContainer" runat="server"
                    class="table table-bordered table-hover">
                    <tr runat="server" style="">
                        <th runat="server" style="width:80px"></th>
                        <th runat="server">City</th>
                    </tr>
                    <tr id="itemPlaceholder" runat="server">
                    </tr>
                </table>
            </LayoutTemplate>
        </asp:ListView>

Now, while the ListView is a "somewhat" more layout, it still follows a HTML table layout. And since each asp control does not require "template" fields, then it is a bit of give and take - but as the markup becomes more complex, then the ListView starts to win due to better layout, and often even less markup.

Note in above where the place holder is for the nested (second table).

Code behind to load above is thus this:

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
            LoadData();
    }


    void LoadData()
    {
        string strSQL = "SELECT City from City ORDER BY City";

        LVCity.DataSource = General.MyRst(strSQL);
        LVCity.DataBind();
    }

And the button click for the one row is thus this:

    protected void cmdCity_Click(object sender, EventArgs e)
    {
        Button cmd = (Button)sender;
        ListViewItem CityRow = (ListViewItem)cmd.NamingContainer;

        Label lblCity = (Label)CityRow.FindControl("lblCity");
        ListView LVHotels = (ListView)CityRow.FindControl("LVHotels");

        string strSQL = "SELECT * FROM tblHotelsA WHERE CITY = @City";
        SqlCommand cmdSQL = new SqlCommand(strSQL);
        cmdSQL.Parameters.Add("@City", SqlDbType.NVarChar).Value = lblCity.Text;

        LVHotels.DataSource = General.MyRstP(cmdSQL);
        LVHotels.DataBind();    

    }

I actually built each ListView seperate, and them "moved" each ListView into the child ListView. So, including above markup, then we have this final markup of 3 nested ListViews

        <asp:ListView ID="LVCity" runat="server">
            <ItemTemplate>
                <tr>
                    <td>
                        <asp:Button ID="cmdCity" runat="server"
                            Text="+"
                            OnClick="cmdCity_Click" />
                    </td>
                    <td>
                        <asp:Label ID="lblCity" runat="server" Text='<%# Eval("City") %>' />
                    </td>
                    <tr>
                        <td colspan="2">
            <asp:ListView ID="LVHotels" runat="server" DataKeyNames="ID">
                <ItemTemplate>
                    <tr>
                        <td style="text-align: center; vertical-align: middle">
                            <asp:Button ID="cmdViewBooked" runat="server"
                                Text="+" class="btn"
                                OnClick="cmdViewBooked_Click" />
                        </td>
                        <td>
                            <asp:Label ID="HotelNameLabel" runat="server" Text='<%# Eval("HotelName") %>' /></td>
                        <td>
                            <asp:Label ID="CityLabel" runat="server" Text='<%# Eval("City") %>' /></td>
                        <td style="width: 350px">
                            <asp:Label ID="DescriptionLabel" runat="server" Text='<%# Eval("Description") %>' />
                        </td>
                        <tr>
                        <td colspan="4">
         <asp:ListView ID="LVBooked" runat="server" DataKeyNames="ID">
            <ItemTemplate>
                <tr>
                    <td><asp:Label ID="FirstName" runat="server" Text='<%# Eval("FirstName")%>' /> </td>
                    <td><asp:Label ID="LastName" runat="server" Text='<%# Eval("LastName") %>' /></td>
                    <td><asp:Label ID="City" runat="server" Text='<%# Eval("City") %>' /></td>
                </tr>
            </ItemTemplate>
            <LayoutTemplate>
                <table id="itemPlaceholderContainer" runat="server"
                    class="table table-bordered table-hover"
                     style="margin-left:125px">
                    <tr runat="server" style="">
                        <th runat="server">FirstName</th>
                        <th runat="server">LastName</th>
                        <th runat="server">City</th>
                    </tr>
                    <tr id="itemPlaceholder" runat="server">
                    </tr>
                </table>
            </LayoutTemplate>
        </asp:ListView>
                            </td>
                        </tr>
                    </tr>
         </ItemTemplate>
        <LayoutTemplate>
            <table id="itemPlaceholderContainer" runat="server"
                class="table table-bordered table-hover">
                <tr runat="server" style="">
                    <th runat="server"></th>
                    <th runat="server">HotelName</th>
                    <th runat="server">City</th>
                    <th runat="server">Description</th>
                </tr>
                <tr id="itemPlaceholder" runat="server">
                </tr>
            </table>
        </LayoutTemplate>
    </asp:ListView>
                        </td>
                    </tr>
                </tr>
            </ItemTemplate>
            <LayoutTemplate>
                <table id="itemPlaceholderContainer" runat="server"
                    class="table table-bordered table-hover">
                    <tr runat="server" style="">
                        <th runat="server" style="width:80px"></th>
                        <th runat="server">City</th>
                    </tr>
                    <tr id="itemPlaceholder" runat="server">
                    </tr>
                </table>
            </LayoutTemplate>
     </asp:ListView>

And now the full code behind listing is this:

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
            LoadData();
    }

    void LoadData()
    {
        string strSQL = "SELECT City from City ORDER BY City";

        LVCity.DataSource = General.MyRst(strSQL);
        LVCity.DataBind();
    }

    protected void cmdCity_Click(object sender, EventArgs e)
    {
        Button cmd = (Button)sender;
        ListViewItem CityRow = (ListViewItem)cmd.NamingContainer;

        Label lblCity = (Label)CityRow.FindControl("lblCity");
        ListView LVHotels = (ListView)CityRow.FindControl("LVHotels");

        string strSQL = "SELECT * FROM tblHotelsA WHERE CITY = @City";
        SqlCommand cmdSQL = new SqlCommand(strSQL);
        cmdSQL.Parameters.Add("@City", SqlDbType.NVarChar).Value = lblCity.Text;

        LVHotels.DataSource = General.MyRstP(cmdSQL);
        LVHotels.DataBind();    

    }

    protected void cmdViewBooked_Click(object sender, EventArgs e)
    {
        Button cmd = (Button)sender;
        ListViewItem HotelRow = (ListViewItem)cmd.NamingContainer;

        // get hidden Hotel PK id
        ListView LVHotels = (ListView)HotelRow.NamingContainer;
        int HotelID = (int)LVHotels.DataKeys[HotelRow.DisplayIndex]["ID"];

        ListView LVBooked = (ListView)HotelRow.FindControl("LVBooked");


        string strSQL =
            "SELECT * FROM People WHERE Hotel_ID = @ID";
        SqlCommand cmdSQL = new SqlCommand(strSQL);
        cmdSQL.Parameters.Add("@ID", SqlDbType.Int).Value = HotelID;

        DataTable dt = General.MyRstP(cmdSQL);

        LVBooked.DataSource = General.MyRstP(cmdSQL);
        LVBooked.DataBind();

    }

And the result is this this:

Keep in mind the SAME approach can be used with a datalist (use of the NamingContainer to get the row click), and hence we don't bother nor care or even try to deal with the built in event model of the datalist (or GridView, or ListView). So, even if you don't adopt ListView, a standard button click event, and use of naming container to pick up the current row click can still be adopted.

I could also with ease have the button click "collapse/hide" the expanded child table.

For completeness, one fast becomes tired of writing code to return a table based on given SQL or SQL command object, so in my general (global) helper class code, I have the following two routines used in above.

    public static DataTable MyRst(string strSQL)
    {
        DataTable rstData = new DataTable();
        using (SqlConnection conn = new SqlConnection(Properties.Settings.Default.TEST4))
        {
            using (SqlCommand cmdSQL = new SqlCommand(strSQL, conn))
            {
                cmdSQL.Connection.Open();
                rstData.Load(cmdSQL.ExecuteReader());
            }
        }
        return rstData;
    }


    public static DataTable MyRstP(SqlCommand cmdSQL)
    {
        DataTable rstData = new DataTable();
        using (SqlConnection conn = new SqlConnection(Properties.Settings.Default.TEST4))
        {
            using (cmdSQL)
            {
                cmdSQL.Connection = conn;
                conn.Open();
                rstData.Load(cmdSQL.ExecuteReader());
            }
        }
        return rstData;
    }

Edit: Adding a show and hide (toggle) of the expanded child tables

To allow a expand, and hide (show + collapse), it is a simple matter to use the .visible property of each child table (ListView).

Hence, for the two child tables, we change the markup to this:

            <asp:ListView ID="LVHotels" runat="server" DataKeyNames="ID"
                visible="false">

and

         <asp:ListView ID="LVBooked" runat="server" DataKeyNames="ID"
             Visible="false" >

Now, in our code behind for the above 2 child tables, we simply add some simple toggle code, and bonus points is we change the button "+" to a "-" to indicate hitting button again will collapse (hide) the child table.

Hence, our two events now become this:

    protected void cmdCity_Click(object sender, EventArgs e)
    {
        Button cmd = (Button)sender;
        ListViewItem CityRow = (ListViewItem)cmd.NamingContainer;

        Label lblCity = (Label)CityRow.FindControl("lblCity");
        ListView LVHotels = (ListView)CityRow.FindControl("LVHotels");

        // if LVHotels is visible, then hide it, and exit

        if (LVHotels.Visible)
        {
            cmd.Text = "+";
            LVHotels.Visible = false;
            return;
        }

        // get here, and table is not visible, show it
        cmd.Text = "-";
        LVHotels.Visible = true;

        string strSQL = "SELECT * FROM tblHotelsA WHERE CITY = @City";
        SqlCommand cmdSQL = new SqlCommand(strSQL);
        cmdSQL.Parameters.Add("@City", SqlDbType.NVarChar).Value = lblCity.Text;

        LVHotels.DataSource = General.MyRstP(cmdSQL);
        LVHotels.DataBind();    

    }

    protected void cmdViewBooked_Click(object sender, EventArgs e)
    {
        Button cmd = (Button)sender;
        ListViewItem HotelRow = (ListViewItem)cmd.NamingContainer;

        // get hidden Hotel PK id
        ListView LVHotels = (ListView)HotelRow.NamingContainer;
        int HotelID = (int)LVHotels.DataKeys[HotelRow.DisplayIndex]["ID"];

        ListView LVBooked = (ListView)HotelRow.FindControl("LVBooked");


        if (LVBooked.Visible)
        {
            cmd.Text = "+";
            LVBooked.Visible = false;
            return;
        }

        // get here, and table is not visible, show it
        LVBooked.Visible=true;
        cmd.Text = "-"; 

        string strSQL =
            "SELECT * FROM People WHERE Hotel_ID = @ID";
        SqlCommand cmdSQL = new SqlCommand(strSQL);
        cmdSQL.Parameters.Add("@ID", SqlDbType.Int).Value = HotelID;

        DataTable dt = General.MyRstP(cmdSQL);

        LVBooked.DataSource = General.MyRstP(cmdSQL);
        LVBooked.DataBind();

    }

And now the result is to expand, we see the button change from a "+" to a "-" and the expanded child table is displayed.

And of course collapsing (hiding) works equally well now:

Separate from the bad TextBox + SQL injection issue, there is a problem with how this code uses the ASP.Net Webforms Page Lifecycle.

Every button click is a complete rebuild of the entire page from scratch, including the base grid. If not done correctly, at the point in the page life cycle where ASP.Net should be registering the click event to fire, the grid elements will not exist yet. If that happens, the event won't fire.

Personally, I think they should make a new exception type specifically for this issue that is thrown when it's time to wire up an event to a control that doesn't exist, with an exception message to explain what's going on.

To resolve it, you have two options:

  1. Make sure your parent grid is re-populated from the database before the events are registered. This means building the grid in the PreInit event instead of the Load event.

    If the main grid itself loads or updates in response to an event — perhaps something like a textbox with a search filter — that also means ensuring that state is saved in a mechanism available at PreInit time.

  2. Refactor to use an UpdatePanel managed from javascript, instead of a nested grid. This can dramatically improve both real and user-perceived performance, because with an UpdatePanel the page in the browser does not unload (no DOM freeze/rebuild) and you only need to query the database for the child data; you don't need to rebuild the rest of the page on the server.

Either way, it's a significant change from the original post. This scenario is one of the reasons webforms has fallen out of favor.

How are you confirming the command is not triggered? Because, while that is possible, there is a separate issue in the code that will prevent you from seeing data even if the code does run.

In the second method, the idcl variable is a Textbox rather than a string. The result is the SQL command will always look like this, no matter what value someone enters in the textbox:

SELECT DISTINCT IdCA,Camera FROM tblCamera WHERE IdCL ='System.Web.UI.WebControls.WebControl.TextBox'

Because it will implicitly call ToString() on the TextBox object, and most objects just output the type name if not explicitly changed to do something else. I doubt there's any data in your database matching that string value.


Of course, the quick/naïve solution is using the control's .Text property to get the desired string value for the SQL statement, instead of the control itself.

But that would be really bad.

The real solution is understanding the GetDataTable() method is fundamentally flawed in a way that is forcing you to write seriously unsafe code. You need to look into using parameterized queries, so the code will look something more like this:

List<SqlParameter> parameters = new() {
    new SqlParameter("@IdCL", SqlDbType.NVarChar, 20)
};
parameters[0].Value = idcl.Text;
secondDataList.DataSource = GetDataTable("SELECT DISTINCT IdCA,Camera FROM tblCamera WHERE IdCL = @IdCL;", parameters );

Here we see I'm imagining a revision to the GetDataTable() method that allows to pass an IEnumerable<SqlParameter> as an additional argument. This isn't the only solution or way to accomplish it, but the important thing is you should NEVER use string concatenation to substitute data into an SQL statement!!

When you have this part working, you need to do same thing for the idob variable in the first method. Fixing this throughout your application should be your top priority.


Again: it is also possible the code is indeed not even running, and I have another idea about why that may be (related to the page life cycle and needing to re-build the outer control in the Pre-Init phase, so events for the nested inner controls can be restored at the right time). But I won't get into that more until we've looked at this other issue, which is a HUGE gaping security problem.

本文标签: cOnItemCommand not firing from nested DataListStack Overflow