skip to Main Content

I am working on a C# webforms application. I have a SQL Server table named HkConv, and it has the following structure:

Column Name Data Type
ID bigint
Qust nvarchar(max)
Answ nvarchar(max)

I use 2 stored procedures that returns the following result sets:

  • SPHkConvSelect to select table records
  • SPHkConvDelete to delete a record from the table

Requirements

  1. When the page loads, I want to display the records in the following format:
<div class="section">
    {Delete Button Here}
    <h2>[ID]: [Qust]</h2>
    <div>
        <h3>[Answ]</h3>
    </div>
</div>

Here, the text in [] represents the column names from the table.

  1. The delete button should call a stored procedure to delete the corresponding record.

What I’ve tried

I am not sure how to structure the code in the webforms page to load the data into the div elements dynamically and handle deletion.

Can someone provide the complete C# webforms code and Ajax to accomplish this?

SQL Server table and data example

Here’s the SQL script to create the table and insert sample data:

-- Create Table
CREATE TABLE HkConv 
(
    ID BIGINT IDENTITY(1,1),
    Qust NVARCHAR(MAX),
    Answ NVARCHAR(MAX),
    Deleted INT DEFAULT 0
);

-- Insert Sample Data
INSERT INTO HkConv (ID, Qust, Answ, Deleted)
VALUES 
(1, 'What is ASP.NET?', 'It is a web framework for building web apps.', 0),
(2, 'What is AJAX?', 'Asynchronous JavaScript and XML.', 0),
(3, 'What is C#?', 'A modern programming language by Microsoft.', 0);

Stored procedures

Get records:

CREATE PROCEDURE SPHkConvSelect
AS
BEGIN
    SELECT ID, Qust, Answ, Deleted 
    FROM HkConv 
    WHERE Deleted = 0;
END
  1. Delete record:
CREATE PROCEDURE SPHkDelete
    @ID BIGINT
AS
BEGIN
    UPDATE HkConv 
    SET Deleted = 1 
    WHERE ID = @ID;
END

Here is my C# aspx.cs page code-behind:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text;
using System.Web.Configuration;

namespace Traffic
{
    public partial class aidash : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                BindData();
            }
        }

        private void BindData()
        {
            string connectionString = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString; // Replace with your connection string name

            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                using (SqlCommand command = new SqlCommand("SPHkConvSelect", connection)) // Replace with your stored procedure name
                {
                    command.CommandType = CommandType.StoredProcedure;
                    using (SqlDataAdapter adapter = new SqlDataAdapter(command))
                    {
                        DataTable dt = new DataTable();
                        adapter.Fill(dt);

                        // Clear existing controls if any
                        pnlData.Controls.Clear();

                        foreach (DataRow row in dt.Rows)
                        {
                            Panel sectionDiv = new Panel();
                            sectionDiv.CssClass = "section";

                            Button deleteButton = new Button();
                            deleteButton.ID = "btnDelete_" + row["ID"].ToString();
                            deleteButton.Text = "Delete (" + row["ID"].ToString() +")" ;
                            deleteButton.CssClass = "deleteButton"; // Add CSS class for styling
                            deleteButton.CommandArgument = row["ID"].ToString(); // Store ID in CommandArgument
                            deleteButton.Click += DeleteButton_Click;
                            sectionDiv.Controls.Add(deleteButton);

                            Label h2Label = new Label();
                            h2Label.Text = $"<h2>{row["ID"]}: {row["Qust"]}</h2>";
                            sectionDiv.Controls.Add(h2Label);

                            Panel innerDiv = new Panel();
                            innerDiv.Controls.Add(new LiteralControl("<div>")); // Start inner div

                            Label h3Label = new Label();
                            h3Label.Text = $"<h3>{row["Answ"]}</h3>";
                            innerDiv.Controls.Add(h3Label);

                            innerDiv.Controls.Add(new LiteralControl("</div>")); // End inner div
                            sectionDiv.Controls.Add(innerDiv);

                            pnlData.Controls.Add(sectionDiv);
                        }
                    }
                }
            }
        }

        protected void DeleteButton_Click(object sender, EventArgs e)
        {
            Button btn = (Button)sender;
            int idToDelete = int.Parse(btn.CommandArgument);

            string connectionString = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;

            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                using (SqlCommand command = new SqlCommand("SPHkConvDelete", connection)) 
                {
                    command.CommandType = CommandType.StoredProcedure;
                    command.Parameters.AddWithValue("@ID", idToDelete);

                    connection.Open();
                    command.ExecuteNonQuery();
                    connection.Close();
                }
            }

            BindData(); 
        }
    }
}

This is the aspx markup:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="aidash.aspx.cs" Inherits="Traffic.aidash" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title></title>
    <style>
        /* Facebook theme styling */
        body {
            font-family: Arial, sans-serif;
            background-color: #f0f2f5;
            color: #1c1e21;
            margin: 0;
            padding: 0;
        }
    </style>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
    <form id="form1" runat="server">
        <asp:Panel ID="pnlData" runat="server"></asp:Panel>
    </form>
</body>
</html>

The problem

When I click the delete button, all records disappear and nothing really gets deleted in the backend.

2

Answers


  1. From the Examples of Button.Click Event, you should provide the EventHandler instance to the Click event.

    deleteButton.Click += new EventHandler(DeleteButton_Click);
    
    Login or Signup to reply.
  2. First up, WebForms has boatloads and boatloads of built in controls for this kind of task.

    So no need to write all that code.

    So, since we want "repeating" data, then of course you can use the "repeater" control. All that control does is "repeat" markup based on a data source.

    Hence, say this markup:

        <asp:Repeater ID="Repeater1" runat="server">
            <ItemTemplate>
                <asp:Button ID="cmdDel" runat="server" Text="Delete"
                    CssClass="btn"
                    OnClick="cmdDel_Click"
                    CommandArgument='<%# Eval("ID") %>'    
                    style="float:left;margin-top:18px"
                    />
    
                <h2 style="float:left;margin-left:20px;">
                    <%# Eval("ID") %>: <%# Eval("Qust") %>
                </h2>
    
                    <h3 style="float:left;margin-left:25px;margin-top:24px">
                        <%# Eval("Answ") %>
                    </h3>
    
                <div style="clear:both"></div>
            </ItemTemplate>
        </asp:Repeater>
    

    Now, you are free to "tweak" the above markup, but the basic concept above is how you bind data into that markup.

    Hence, the code behind to load up the data now becomes this:

        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
                LoadData();
        }
    
        void LoadData()
        {
            string strSQL =
                "SELECT * FROM hKcONV order by ID";
    
            Repeater1.DataSource = General.MyRst(strSQL);
            Repeater1.DataBind();
    
        }
    

    And now the result is this:

    enter image description here

    As for the button click? Well, you can actually pick up any element within the given row click, but simply adding the PK ID to the command argument attribute of the button works well.

    Hence, code to delete a row can be:

        protected void cmdDel_Click(object sender, EventArgs e)
        {
            Button cmdDel = (Button)sender;
    
            long PK = long.Parse(cmdDel.CommandArgument);
    
            SqlCommand cmdMyDel = new SqlCommand("SPHkDelete");
            cmdMyDel.CommandType = CommandType.StoredProcedure;
            cmdMyDel.Parameters.Add("@ID", SqlDbType.BigInt).Value = PK;
    
            General.MyRstE(cmdMyDel);
            LoadData(); // reload grid to reflect changes
    
        }
    

    Of course, one becomes really tired of typing code over and over to pull data, or call some stored procedure, so I have a few global helpers in my general class of which I used in above, they are:

        public static DataTable MyRst(string strSQL, string sConn = "")
        {
            DataTable rstData = new DataTable();
    
            if (sConn == "")
                sConn = Properties.Settings.Default.TEST4;
    
            using (SqlConnection conn = new SqlConnection(sConn))
            {
                using (SqlCommand cmdSQL = new SqlCommand(strSQL, conn))
                {
                    cmdSQL.Connection.Open();
                    rstData.Load(cmdSQL.ExecuteReader());
                    rstData.TableName = strSQL;
                }
            }
            return rstData;
        }
    
    public static DataTable MyRstP(SqlCommand cmdSQL, string strCon = "")
    {
            DataTable rstData = new DataTable();
    
            if (strCon == "")
                strCon = Properties.Settings.Default.TEST4;
    
            using (SqlConnection conn = new SqlConnection(strCon))
            {
                using (cmdSQL)
                {
                    cmdSQL.Connection = conn;
                    conn.Open();
                    rstData.Load(cmdSQL.ExecuteReader());
                }
            }
            return rstData;
        }
    
    
        public static void MyRstE(SqlCommand cmdSQL, string sCon = "")
        {
            if (sCon == "")
                sCon = Properties.Settings.Default.TEST4;
    
            using (SqlConnection conn = new SqlConnection(sCon))
            {
                using (cmdSQL)
                {
                    cmdSQL.Connection = conn;
                    conn.Open();
                    cmdSQL.ExecuteNonQuery();
                }
            }
        }
    

    So note a few things:

    We don’t have to write looping code, it simply not required.

    We don’t have to write code to attach some event to the button. You have a standard button click event, and you can pass the given row PK value using the CommandArugment attribuite of the button.

    And by using the repeater, then you write a minimal amount of markup, and simply let the "repeater" repeat that markup over and over.

    I would also consider a GridView, or more flexible would be to consider a ListView. These controls allow hiding of the PK value, and for a lot of repeating data displays (such as grid’s and tables), then not exposing the PK from the database is a welcome security measure.

    Also, since the button is nested inside of the Repeater control, then a simple double click on the button (in the designer view) will not create the code behind (click) stub for you. Hence, you have to do this in markup, but intel-sense will and can still create the click event for you. You can do this by entering OnClick=" (then hit space bar, and you given a choice to select a existing event, or create a new one).

    The process looks like this:

    enter image description here

    So, when you hit "=" in the markup, then intel-sense should offer you to create the click event for that button. When you now flip back to code behind view, you find that button click code stub having been created for you.

    Hence, for nested controls inside of data bound controls, then you can (should) use the above approach to create a click event for controls inside of data bound controls.

    For buttons not nested inside such controls, then a simple double click on such buttons in design view will suffice to create the click event.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search