ASP.NET  

CRUD (Create, Read, Update, Delete) with Fetch API in Vanilla ASP.NET Web Forms (Vanilla Web Forms)

This article will introduce the basics of doing CRUD in Vanilla ASP.NET Web Forms Architecture.

Modern yet foundational.

CRUD = Create, Read, Update, Delete. The synonyms for SELECT, INSERT, UPDATE, and DELETE (the database operations).

In Vanilla Web Forms Architecture, we handle the communication of data exchange with the Web Server manually by using pure foundation and native web technology, where all current modern web frameworks build on top off. We do it directly at the core of every web framework (including ASP.NET MVC, Core) that we work on. This method will reveal and strip off all the abstraction layer and let you see the real deal that is actually working under the hood.

Building the Frontend Form

Let’s start with the frontend first. Write a basic html form so that we can navigate the idea with a foundation of data.

Simple Basic HTML Form

User Information

Note. The CSS styling for this form is at the end of this article.

We’ll use this input form for this tutorial:

<div id="divUser" class="form-container">
    <h2 class="form-title">User Information</h2>

    <div id="userForm" class="form-grid">

        <input type="hidden" id="inputHiddenUserId" name="user_id">

        <div class="form-group">
            <label for="spanId">User ID</label>
            <span id="spanId">[New User]</span>
        </div>

        <div class="form-group">
            <label for="inputEmail">Email Address</label>
            <input type="email" id="inputEmail" name="email" required>
        </div>

        <div class="form-row">
            <div class="form-group">
                <label for="inputFirstName">First Name</label>
                <input type="text" id="inputFirstName" name="first_name" required>
            </div>
            <div class="form-group">
                <label for="inputLastName">Last Name</label>
                <input type="text" id="inputLastName" name="last_name" required>
            </div>
        </div>

        <div class="form-row">
            <div class="form-group">
                <label for="inputPhone">Phone Number</label>
                <input type="tel" id="inputPhone" name="phone">
            </div>
            <div class="form-group">
                <label for="selectCountry">Country</label>
                <select id="selectCountry" name="country_id">
                    <option value="0">Select Country</option>
                    <option value="1">Atreides</option>
                    <option value="2">Harkonnen</option>
                    <option value="3">Corrino</option>
                    <option value="4">Fenring</option>
                </select>
            </div>
        </div>

        <div class="button-group">
            <button type="button" class="btn-primary" onclick="saveUser()">Save</button>
            <button type="button" class="btn-primary" onclick="resetUI()">New</button>
            <button type="button" class="btn-primary" onclick="deleteUser()">Delete</button>
        </div>
    </div>
</div>

By default, this

<button>Save</button>

Is equivalent to

<button type="submit">Save</button>

Which is doing a full postback and page refresh, but since we are manually handling the communication, we’ll be using the real “button”, which has already been shown in the HTML example above. The declaration of “button” as a type attribute is a small but important step in this context.

<button type="button">Save</button>
<button type="button">New</button>
<button type="button">Delete</button>

There are 2 ways to attach a JavaScript function to buttons (or any elements).

Using the attribute of “onclick”. This method allows you to attach a single-function event.

<button type="button" onclick="saveUser()">Save</button>

<script>
    async function saveUser() {
        // sending data to backend server
    }
</script>

Or attach an event listener. This method allows you to attach multiple function events, but em… It’s not rational in this context… why would anyone want to attach multiple save events to a single click? In some rare cases, you want to do multiple things… but you can do that too with an asynchronous method in a single function. Assigning multiple save events to a single button click sounds more like a bug (accidental logic error). There is another rational reason, which is the requirement to change the assigned function to a different function.

From the software engineering perspective, there is a concept called separation of concerns, where action assignments are often separated from designing the HTML, especially if this is working in a team, where the designer and coding team are separated.

// pure design
<button type="button" id="btSave">Save</button>

// pure action/javascript
<script>
    document.getElementById('btSave').addEventListener("click", () => { saveUser(); });

    // or

    document.getElementById('btSave').addEventListener("click", saveUser);

    // or 

    // passing the event as argument/parameter into the function

    document.getElementById('btSave').addEventListener("click", (e)=> { saveUser(); });

    // or

    document.getElementById('btSave').addEventListener("click", function(e)=> { saveUser(); });

    async saveUser() {
        // sending data to backend server
    }
</script>

Sending Request to Server (Backup API Endpoint)

Right now, we’ll focus on one round trip to the server and back. Every other function works the same. We’ll continue the tutorial at the save() function.

Let’s introduce one of the most related typical ways in ASP.NET Web Forms context.

Using Fetch API to submit/send data to the backend. Read more: (mdn web docs) Using the Fetch API

Introducing 2 of the ways to send data:

  • Form Data Post
  • JSON Data

Form Data Post Request with Fetch API

// assuming you have a page routed to the url "/apiUser"
// which in ASP.NET Web Forms, it originates from "/apiUser.aspx"
// We'll be discussing in details on the creation of the backend later in this acticle

let urlApiEndpoint = "/apiUser";

// caching all the input fields globally

let divUser = document.querySelector("#divUser");
let inputHiddenUserId = document.querySelector("#inputHiddenUserId");
let spanId = document.querySelector("#spanId");
let inputEmail = document.querySelector("#inputEmail");
let inputFirstName = document.querySelector("#inputFirstName");
let inputLastName = document.querySelector("#inputLastName");
let inputPhone = document.querySelector("#inputPhone");
let selectCountry = document.querySelector("#selectCountry");

async function saveUser() {

    // doing a typical form post

    const formData = new FormData();

    // collecting form values

    const formData = new FormData();

    // setting the action:
    formData.append("action", "saveuser");

    // collecting form values

    // -----------------------------
    // method 1: manually
    // -----------------------------
    formData.append("user_id", inputHiddenUserId.value);
    formData.append("email", inputEmail.value);
    formData.append("first_name", inputFirstName.value);
    formData.append("last_name", inputLastName.value);
    formData.append("phone", inputPhone.value);
    formData.append("country_id", selectCountry.value);

    // -----------------------------
    // method 2: auto - Get all input and select elements within divUser
    // -----------------------------
    const inputs = divUser.querySelectorAll("input, select");
    inputs.forEach(element => {
        // Only collect elements that have a 'name' attribute
        if (element.name) {
            formData.append(element.name, element.value);
        }
    });

    try
    {
        // -----------------------------    
        // sending request to the server
        // -----------------------------
        const response = await fetch(urlApiEndpoint, {
            method: 'POST',
            body: formData,
            credentials: 'include' // include user login credentials
        });

        // response returning from the server/backend
        if (response.ok) {

            // response.ok = response.status = 200 (default)
            // success, everything is good
            // assume the server is returning JSON object

            const jsonObject = await response.json();

            // update the UI, indicates a new member is created in database

            spanId.textContent = jsonObject.Id;

            // custom javascript message box
            showSuccessMessage("Data Saved Successfully");

            // custom message box
            // https://adriancs.com/showing-simple-javascript-message-notification-box/
        }
        else {
            // error

            // get the text error message
            let err = response.text();
            let errMsg = `${response.status} - ${err}`;

            // example output:
            // 500 - Interval Server Error

            console.log(errMsg);

            // custom javascript message box
            showErrorMessage(errMsg);

            // custom message box
            // https://adriancs.com/showing-simple-javascript-message-notification-box/
        }
    }
    catch (error) {
        console.error('Network error:', error);
        showErrorMessage("Network error occurred");
    }
}

JSON Data Request

async function saveUser() {

    // Create JavaScript object
    
    // add the action after initialization
    const jsonData = {};
    jsonData.action = "saveuser";
    
    // add the "action" during initialization
    const jsonData = { action: "saveuser" };

    // setting the action:
    jsonData.action = "saveuser";

    // collecting form values
    
    // -----------------------------
    // method 1: manually
    // -----------------------------
    jsonData.user_id = inputHiddenUserId.value;
    jsonData.email = inputEmail.value;
    jsonData.first_name = inputFirstName.value;
    jsonData.last_name = inputLastName.value;
    jsonData.phone = inputPhone.value;
    jsonData.country_id = selectCountry.value;

    // -----------------------------
    // method 2: auto - Get all input and select elements within divUser
    // -----------------------------
    const inputs = divUser.querySelectorAll("input, select");
    inputs.forEach(element => {
        // Only collect elements that have a 'name' attribute
        if (element.name) {
            jsonData[element.name] = element.value;
        }
    });

    try
    {
        // -----------------------------
        // sending request to the server
        // -----------------------------
        const response = await fetch(urlApiEndpoint, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(jsonData),
            credentials: 'include' // include user login credentials
        });
        
        // response returning from the server/backend
        if (response.ok) {
        
            ... the rest here is same as the form data post
        }
        else {
            
            ... the rest here is same as the form data post
        }
    }
    catch (error) {
        console.error('Network error:', error);
        showErrorMessage("Network error occurred");
    }
}

Now, we’ll move to the backend. Let’s build the backend API endpoint.

Create a new blank Web Forms page, which will look like this:

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

<!DOCTYPE html>

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

Delete all the frontend markup and leave only the first line of the page directive:

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

Go to the code behind, the backend, and edit the api action handlers.

Initial code behind:

public partial class apiUser : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {

    }
}

Add the action handler:

public partial class apiUser : System.Web.UI.Page
{
    string constr = "server=localhost;user=root;pwd=password;database=membership;";

    protected void Page_Load(object sender, EventArgs e)
    {
        string action = (Request["action"] + "").ToLower();

        switch (action)
        {
            case "saveuser":
                SaveUser();
                break;
        }
    }

    void SaveUser()
    {
        int userid = 0;

        int.TryParse(Request["user_id"] + "", out userid);
        int.TryParse(Request["country_id"] + "", out int country_id);

        string email = Request["email"] + "";
        string first_name = Request["first_name"] + "";
        string last_name = Request["last_name"] + "";
        string phone = Request["phone"] + "";

        // Next, saving data to database
        // We'll be using MySQL and MySqlExpress in this demo.

        using (var conn = new MySqlConnection(constr))
        using (var cmd = conn.CreateCommand())
        {
            conn.Open();

            var m = new MySqlExpress(cmd);

            var dic = new Dictionary<string, object>();

            dic["email"] = email;
            dic["first_name"] = first_name;
            dic["last_name"] = last_name;
            dic["phone"] = phone;
            dic["country_id"] = country_id;

            bool performInsert = true;

            if (userid == 0)
            {
                performInsert = true;
            }
            else
            {
                // check if the user_id actually exists

                // use parameterized query
                var dicParam = new Dictionary<string, object>();
                dicParam["@id"] = userid;

                string sqlSelectCount = "select count(*) from `user` where id=@id";

                int user_exist = m.ExecuteScalar<int>(sqlSelectCount, dicParam);

                if (user_exist > 0)
                {
                    // ok, the user_id existed, perform update
                    performInsert = false;
                }
                else
                {
                    // nope, the provided user_id is not existed, perform insert as new record
                    performInsert = true;
                }
            }

            if (performInsert)
            {
                // new user
                m.Insert("user", dic);
                userid = m.LastInsertId;
            }
            else
            {
                m.Update("user", dic, "id", userid);
            }
        }

        // creates an Anonymous object

        var result = new
        {
            UserId = userid;
        }

        // convert the Anonymous object "result" into json string 

        // Install the Nuget Package of "System.Text.JSON" to enable this function
        // You also need to add an 'using' line at the top of your code to use this:
        // using System.Text.JSON;

        string json = JsonSerializer.Serialize(result);
        Response.ContentType = "application/json";

        // send the progress status back to the frontend
        Response.Write(json);
    }
}

You’ll notice there is a tool/library call MySqlExpress used in the demo. It is an open-source library helper.

Read more: Github – MySqlExpress

Backend API Boilerplate / Basic Template

Now, let’s create the full template for handling the remaining action requests (saveuser, getuser, deleteuser)

We’ll now explore different kinds of entry points, means, how the frontend submits the data, and how the backend should handle each kind of submission technique.

Style 1. Declare action in descriptive string (Form Data/Query String)

{ "action" : "save" }

Easy to understand by humans, code maintenance and debugging are friendly.

namespace myweb
{
    public partial class apiUser : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!UserIsAuthenticated())
            {
                // HTTP Error: 401 Unauthorized
                Response.StatusCode = 401;
                Response.Write("Unauthorized");
                return;
            }

            string action = (Request["action"] + "").ToLower();

            switch (action)
            {
                case "saveuser":
                    SaveUser();
                    break;
                case "getuser":
                    GetUser();
                    break;
                case "deleteuser":
                    DeleteUser();
                    break;
                case "":
                    // empty 
                    // HTTP Error: 400 Bad Request
                    Response.StatusCode = 400;
                    Response.Write("Bad Request");
                    break;
                default:
                    // any other unknown parameter
                    // HTTP Error: 405 Method Not Allowed
                    Response.StatusCode = 405;
                    Response.Write("Method Not Allowed");
                    break;
            }
        }

        bool UserIsAuthenticated()
        {
            // Placeholder implementation - replace with actual authentication logic
            // Temporarily returns true to simplify demo functionality
            return true;
        }

        void SaveUser()
        {
            try
            {
                ....
            }
            catch (Exception ex)
            {
                // HTTP Error: 500 Internal Server Error
                Response.StatusCode = 500;
                Response.Write(ex.Message);
            }
        }

        void GetUser()
        {
            try
            {
                ....
            }
            catch (Exception ex)
            {
                // HTTP Error: 500 Internal Server Error
                Response.StatusCode = 500;
                Response.Write(ex.Message);
            }
        }

        void DeleteUser()
        {
            try
            {
                ....
            }
            catch (Exception ex)
            {
                // HTTP Error: 500 Internal Server Error
                Response.StatusCode = 500;
                Response.Write(ex.Message);
            }
        }
    }
}

About HTTP Status Code

From the above code, you’ll notice that there are different kinds of Status codes (401, 405, 500).

Those are standard web protocols used in web communication. It doesn’t necessarily have to use the exact representation of error in our case, because at the frontend, we are only concerned about whether it is a “Ok/Success” or “Not Ok/Error”. You can always use the same error code 500 everywhere to indicate an error. At the frontend, we are not utilizing the error code, so it doesn’t matter what kind of error it is. For more info about different kinds of status codes (success and error), please read: mdn web docs – HTTP response status codes

Style 2: Declare action in number (Form Data/Query String)

{ "action": 1 }

Network data transmission efficiency, machine-friendly.

protected void Page_Load(object sender, EventArgs e)
{
    if (!UserIsAuthenticated())
    {
        // HTTP Error: 401 Unauthorized
        Response.StatusCode = 401;
        Response.Write("401 Unauthorized");
        return;
    }

    if (int.TryParse(Request["action"] + ""), out int actionid)
    {
        switch (action)
        {
            case 1:
                Save();
                break;
            case 2:
                Get();
                break;
            case 3:
                Delete();
                break;
            case 0:
                // empty 
                // HTTP Error: 400 Bad Request
                Response.StatusCode = 400;
                Response.Write("Bad Request");
                break;
            default:
                // any other unknown parameter
                // HTTP Error: 405 Method Not Allowed
                Response.StatusCode = 405;
                Response.Write("Method Not Allowed");
                break;
        }
    }
    else
    {
        // Not a number, invalid request
        // 400 Bad Request
        Response.StatusCode = 400;
        Response.Write("Bad Request");
    }
}

Method 3. Accept the Request Context in Body Payload

Request action set int request body payload, not form data or query string. Typically custom formatting (such as CSV), or not from a conventional web browser request origin (typically an IoT or Device origin where it streams the data directly to the server).

Before we proceed, it might be a good time to finally write the class for the user object. We will use the following format as it will work with MySqlExpress, where the private fields will match the database column’s names, and the public properties will be used to align with C# coding convention.

public class User
{
    // for database use

    private int user_id = 0;
    private string first_name = "";
    private string last_name = "";
    private string email = "";
    private string phone = "";
    private int country_id = 0;

    public int UserId { get { return user_id; } set { user_id = value; } }
    public string FirstName { get { return first_name; } set { first_name = value; } }
    public string LastName { get { return last_name; } set { last_name = value; } }
    public string Email { get { return email; } set { email = value; } }
    public string Phone { get { return phone; } set { phone = value; } }
    public int CountryId { get { return country_id; } set { country_id = value; } }


    // for server use

    private string action = "";
    private int action_id = 0;

    public string Action { get { return action; } set { action = value; }
    public string ActionId { get { return action_id; } set { action_id = value; }
}

The C# backend for handling body payload data:

protected void Page_Load(object sender, EventArgs e)
{
    if (!UserIsAuthenticated())
    {
        // HTTP Error: 401 Unauthorized
        Response.StatusCode = 401;
        Response.Write("401 Unauthorized");
        return;
    }

    // obtaining the body payload

    string bodyContent = "";

    // --------------------------------------------
    // method 1: Use BinaryRead()
    // --------------------------------------------
    byte[] bytes = Request.BinaryRead(Request.ContentLength);
    bodyContent = Encoding.UTF8.GetString(bytes);

    // --------------------------------------------
    // method 2: Use StreamReader
    // --------------------------------------------
    using (var reader = new StreamReader(Request.InputStream))
    {
        bodyContent = reader.ReadToEnd();
    }

    // Declare user class object to hold data:
    User u = new User();

    // different types of payload
    // Type 1: CSV (comman separated values)
    // Type 2: JSON formatted string
    // Type 3: XML document
    // Type 4: Form Data (Form Post)

    // --------------------------------------------
    // Type 1: CSV
    // --------------------------------------------
    // Expected format: action|user_id|first_name|last_name|email|phone|country_id
    // Example: "saveuser|123|John|Doe|[email protected]|555-1234|1"

    string[] csvValues = bodyContent.Split('|');

    int.TryParse(csvValues[1].Trim(), out int userId);
    int.TryParse(csvValues[6].Trim(), out int countryId);

    u.Action = csvValues[0].Trim();
    u.UserId = userId;
    u.FirstName = csvValues[2].Trim();
    u.LastName = csvValues[3].Trim();
    u.Email = csvValues[4].Trim();
    u.Phone = csvValues[5].Trim();
    u.CountryId = countryId;

    // --------------------------------------------
    // Type 2: JSON formatted string
    // --------------------------------------------
    // Expected format: {"UserId":123,"FirstName":"John","LastName":"Doe","Email":"[email protected]","Phone":"555-1234","CountryId":1}

    // done in just one line
    u = JsonSerializer.Deserialize<User>(bodyContent)

    // --------------------------------------------
    // Type 3: XML document
    // --------------------------------------------
    
    // another one line
    u = XmlHelper.Deserialize<User>(bodyContent);

    // --------------------------------------------
    // Type 4: Form Data Post (or simply Form Post)
    // --------------------------------------------

    // Same, done in just one line, with a helper method
    u = ParseFormDataToObject<User>(Request);

    // or finally, the old school way without using helper
    // get the values one by one
    
    int.TryParse(Request["user_id"] + "", out int uid);
    int.TryParse(Request["country_id"] + "", out int countryid);
    
    u.Action = Request["action"] + "";
    u.UserId = uid;
    u.FirstName = Request["first_name"] + "";
    u.LastName = Request["last_name"] + "";
    u.Email = Request["email"] + "";
    u.Phone = Request["phone"] + "";
    u.CountryId = countryid;

    switch (u.Action)
    {
        case "saveuser":
            SaveUser(u);
            break;
        case "getuser":
            GetUser(u);
            break;
        case "deleteuser":
            DeleteUser(u);
            break;
        case "":
            // empty 
            // HTTP Error: 400 Bad Request
            Response.StatusCode = 400;
            Response.Write("Bad Request");
            break;
        default:
            // any other unknown parameter
            // HTTP Error: 405 Method Not Allowed
            Response.StatusCode = 405;
            Response.Write("Method Not Allowed");
            break;
    }
}

The ParseFormDataToObject(Request) Helper Method

public static T ParseFormDataToObject<T>(HttpRequest request)
{
    var obj = new T();
    var type = typeof(T);
    
    // Loop through all form keys once
    foreach (string key in request.Form.AllKeys)
    {
        if (string.IsNullOrEmpty(key)) continue;
        
        string value = request.Form[key];
        if (string.IsNullOrEmpty(value)) continue;
        
        // Try to find matching property
        var prop = type.GetProperty(key);
        if (prop != null && prop.CanWrite)
        {
            try
            {
                var convertedValue = Convert.ChangeType(value, prop.PropertyType);
                prop.SetValue(obj, convertedValue);
                continue;
            }
            catch { }
        }
        
        // Try to find matching field
        var field = type.GetField(key, BindingFlags.NonPublic | BindingFlags.Instance);
        if (field != null)
        {
            try
            {
                var convertedValue = Convert.ChangeType(value, field.FieldType);
                field.SetValue(obj, convertedValue);
            }
            catch { }
        }
    }
    
 
    return obj;
}

Save User (Method 1. Save Class Object Directly)

void SaveUser(User u)
{
    try
    {
        using (var conn = new MySqlConnection(constr))
        using (var cmd = conn.CreateCommand())
        {
            conn.Open();

            var m = new MySqlExpress(cmd);

            bool performInsert = true;

            if (u.UserId == 0)
            {
                performInsert = true;
            }
            else
            {
                var dicParam = new Dictionary<string, object>();
                dicParam["@id"] = u.UserId;

                string sqlSelectCount = "select count(*) from `user` where id=@id";

                int user_exist = m.ExecuteScalar<int>(sqlSelectCount, dicParam);

                if (user_exist > 0)
                {
                    performInsert = false;
                }
                else
                {
                    // reset the user id
                    u.UserId = 0;
                    performInsert = true;
                }
            }

            // do INSERT or UPDATE at the same time
            m.Save("user", u);

            if (performInsert)
            {
                u.UserId = m.LastInsertId;
            }
        }

        var result = new
        {
            UserId = u.UserId;
        }

        string json = JsonSerializer.Serialize(result);
        Response.ContentType = "application/json";
        Response.Write(json);
    }
    catch (Exception ex)
    {
        // HTTP Error: 500 Internal Server Error
        Response.StatusCode = 500;
        Response.Write(ex.Message);
    }
}

Save User (Method 2. Use Dictionary with MySqlExpress)

void SaveUser(User u)
{
    try
    {
        using (var conn = new MySqlConnection(constr))
        using (var cmd = conn.CreateCommand())
        {
            conn.Open();

            var m = new MySqlExpress(cmd);

            bool performInsert = true;

            if (u.UserId == 0)
            {
                performInsert = true;
            }
            else
            {
                var dicParam = new Dictionary<string, object>();
                dicParam["@id"] = u.UserId;

                string sqlSelectCount = "select count(*) from `user` where id=@id";

                int user_exist = m.ExecuteScalar<int>(sqlSelectCount, dicParam);

                if (user_exist > 0)
                {
                    performInsert = false;
                }
                else
                {
                    // reset the user id
                    u.UserId = 0;
                    performInsert = true;
                }
            }

            // use a dictionary to manually collect the column:values
            var dic = new Dictionary<string, object>();

            dic["email"] = u.Email;
            dic["first_name"] = u.FirstName;
            dic["last_name"] = u.LastName;
            dic["phone"] = u.Phone;
            dic["country_id"] = u.CountryId;

            if (performInsert)
            {
                // new user
                m.Insert("user", dic);
                u.UserId = m.LastInsertId;
            }
            else
            {
                m.Update("user", dic, "id", u.UserId);
            }
        }

        var result = new
        {
            UserId = u.UserId;
        }

        string json = JsonSerializer.Serialize(result);
        Response.ContentType = "application/json";
        Response.Write(json);
    }
    catch (Exception ex)
    {
        // HTTP Error: 500 Internal Server Error
        Response.StatusCode = 500;
        Response.Write(ex.Message);
    }
}

Get User

void GetUser(User u)
{
    try
    {
        User u = new User();

        using (var conn = new MySqlConnection(constr))
        using (var cmd = conn.CreateCommand())
        {
            conn.Open();

            var m = new MySqlExpress(cmd);

            var dicParam = new Dictionary<string, object>();
            dicParam["@id"] = userid;

            string sqlSelect = "select * from `user` where id=@id";

            u = m.GetObject<User>(sqlSelect, dicParam);
        }

        string json = JsonSerializer.Serialize(u);
        Response.ContentType = "application/json";
        Response.Write(json);
    }
    catch (Exception ex)
    {
        // HTTP Error: 500 Internal Server Error
        Response.StatusCode = 500;
        Response.Write(ex.Message);
    }
}

Delete User

void DeleteUser(User u)
{
    if (u.UserId == 0)
    {
        // HTTP Error: 416 Range Not Satisfiable
        Response.StatusCode = 416;
        Response.Write($"Invalid User ID: {u.UserId}");
        return;
    }

    try
    {
        using (var conn = new MySqlConnection(constr))
        using (var cmd = conn.CreateCommand())
        {
            conn.Open();

            var m = new MySqlExpress(cmd);

            var dicParam = new Dictionary<string, object>();
            dicParam["@id"] = u.UserId;

            string sqlSelectCount = "select count(*) from `user` where id=@id";

            int user_exist = m.ExecuteScalar<int>(sqlSelectCount, dicParam);

            if (user_exist > 0)
            {
                string sqlDelete = "delete from `user` where id=@id;";
                m.Execute(sqlDelete, dicParam);
            }
            else
            {
                conn.Close();
                // HTTP Error: 400 Bad Request
                Response.StatusCode = 400;
                Response.Write($"User ID not existed: {u.UserId}");
                return;
            }

        }

        string json = JsonSerializer.Serialize(u);
        Response.ContentType = "application/json";
        Response.Write(json);
    }
    catch (Exception ex)
    {
        // HTTP Error: 500 Internal Server Error
        Response.StatusCode = 500;
        Response.Write(ex.Message);
    }
}

Now, we’ll move to the frontend to complete the circle, handle the rest of the function to call the api endpoint:

// JavaScript - Frontend

async function getUser() {

    // get the user id from the hidden field
    let userId = inputHiddenUserId.value;

    if (!userId || userId == "0") {
        showErrorMessage("Please provide a User ID to retrieve");
        return;
    }

    // Create JavaScript object for JSON request
    const jsonData = {
        action: "getuser",
        user_id: userId
    };

    try {
        // sending request to the server
        const response = await fetch(urlApiEndpoint, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(jsonData),
            credentials: 'include' // include user login credentials
        });

        // response returning from the server/backend
        if (response.ok) {
            // success, everything is good
            // server is returning User JSON object
            const user = await response.json();

            // populate the form with user data
            inputHiddenUserId.value = user.UserId || user.user_id;
            spanId.textContent = user.UserId || user.user_id;
            inputEmail.value = user.Email || user.email;
            inputFirstName.value = user.FirstName || user.first_name;
            inputLastName.value = user.LastName || user.last_name;
            inputPhone.value = user.Phone || user.phone;
            selectCountry.value = user.CountryId || user.country_id;

            // custom javascript message box
            showSuccessMessage("User data loaded successfully");
        }
        else {
            // error
            let err = await response.text();
            let errMsg = `${response.status} - ${err}`;

            console.log(errMsg);
            showErrorMessage(errMsg);
        }
    }
    catch (error) {
        console.error('Network error:', error);
        showErrorMessage("Network error occurred");
    }
}

Delete User

async function deleteUser() {

    // get the user id from the hidden field
    let userId = inputHiddenUserId.value;

    if (!userId || userId == "0") {
        showErrorMessage("Please select a user to delete");
        return;
    }

    // confirm before deletion
    if (!confirm("Are you sure you want to delete this user?")) {
        return;
    }

    // Create JavaScript object for JSON request
    const jsonData = {
        action: "deleteuser",
        user_id: userId
    };

    try {
        // sending request to the server
        const response = await fetch(urlApiEndpoint, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(jsonData),
            credentials: 'include' // include user login credentials
        });

        // response returning from the server/backend
        if (response.ok) {
            // success, user deleted
            const result = await response.json();

            // clear the form after successful deletion
            resetUI();

            // custom javascript message box
            showSuccessMessage("User deleted successfully");
        }
        else {
            // error
            let err = await response.text();
            let errMsg = `${response.status} - ${err}`;

            console.log(errMsg);
            showErrorMessage(errMsg);
        }
    }
    catch (error) {
        console.error('Network error:', error);
        showErrorMessage("Network error occurred");
    }
}

Reset UI / New Data

// Helper function to reset the UI to new user state
function resetUI() {
    inputHiddenUserId.value = "0";
    spanId.textContent = "[New User]";
    inputEmail.value = "";
    inputFirstName.value = "";
    inputLastName.value = "";
    inputPhone.value = "";
    selectCountry.value = "0";
}

The CSS that styles the HTML input form

Example of CSS style used in this article.

* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

:root {
    /* Dark theme variables */
    --bg-primary: #0d1117;
    --bg-secondary: #161b22;
    --text-primary: #f0f6fc;
    --text-secondary: #8b949e;
    --border-color: #30363d;
    --border-focus: #58a6ff;
    --button-primary-bg: #238636;
    --button-primary-bg-hover: #2ea043;
    --button-secondary-bg: #21262d;
    --button-secondary-bg-hover: #30363d;
    --shadow: rgba(0, 0, 0, 0.4);
    --input-bg: #0d1117;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background-color: var(--bg-primary);
    color: var(--text-primary);
    padding: 20px;
    transition: background-color 0.3s ease, color 0.3s ease;
}

.theme-toggle {
    position: fixed;
    top: 20px;
    right: 20px;
    background: var(--button-secondary-bg);
    color: var(--text-primary);
    border: 2px solid var(--border-color);
    border-radius: 50px;
    padding: 12px 16px;
    cursor: pointer;
    font-size: 14px;
    font-weight: 500;
    transition: all 0.3s ease;
    z-index: 1000;
    display: flex;
    align-items: center;
    gap: 8px;
}

.theme-toggle:hover {
    background: var(--button-secondary-bg-hover);
    border-color: var(--border-focus);
    transform: translateY(-2px);
}

.theme-icon {
    font-size: 16px;
}

.form-container {
    max-width: 600px;
    margin: 0 auto;
    background: var(--bg-secondary);
    padding: 2rem;
    border-radius: 12px;
    box-shadow: 0 4px 20px var(--shadow);
    border: 1px solid var(--border-color);
    transition: all 0.3s ease;
}

.form-title {
    margin-bottom: 1.5rem;
    color: var(--text-primary);
    text-align: center;
    font-size: 1.8rem;
    font-weight: 600;
}

.form-grid {
    display: grid;
    gap: 1.5rem;
}

.form-group {
    display: flex;
    flex-direction: column;
}

.form-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
}

label {
    margin-bottom: 0.5rem;
    font-weight: 500;
    color: var(--text-secondary);
}

#spanId {
    padding: 0.75rem;
    background: var(--input-bg);
    border: 2px solid var(--border-color);
    border-radius: 6px;
    color: var(--text-primary);
    font-style: italic;
    transition: all 0.3s ease;
}

input[type="text"],
input[type="email"],
input[type="tel"],
input[type="password"],
select,
textarea {
    padding: 0.75rem;
    border: 2px solid var(--border-color);
    border-radius: 6px;
    font-size: 1rem;
    background: var(--input-bg);
    color: var(--text-primary);
    transition: all 0.3s ease;
}

input:focus,
select:focus,
textarea:focus {
    outline: none;
    border-color: var(--border-focus);
    box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.1);
}

select option {
    background: var(--bg-secondary);
    color: var(--text-primary);
}

textarea {
    resize: vertical;
    min-height: 100px;
}

.button-group {
    display: flex;
    gap: 1rem;
    justify-content: flex-end;
    margin-top: 1rem;
}

button {
    padding: 0.75rem 1.5rem;
    border: none;
    border-radius: 6px;
    font-size: 1rem;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.3s ease;
    position: relative;
    overflow: hidden;
}

.btn-primary {
    background-color: var(--button-primary-bg);
    color: white;
}

.btn-primary:hover {
    background-color: var(--button-primary-bg-hover);
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(35, 134, 54, 0.3);
}

.btn-secondary {
    background-color: var(--button-secondary-bg);
    color: var(--text-primary);
    border: 1px solid var(--border-color);
}

.btn-secondary:hover {
    background-color: var(--button-secondary-bg-hover);
    transform: translateY(-2px);
}

/* Hover effects */
.form-container:hover {
    box-shadow: 0 8px 25px var(--shadow);
}

input:hover,
select:hover {
    border-color: var(--border-focus);
}

/* Responsive design */
@media (max-width: 768px) {
    .form-row {
        grid-template-columns: 1fr;
    }

    .button-group {
        justify-content: stretch;
        flex-direction: column;
    }

    button {
        flex: 1;
    }
}

/* Animation for theme switching */
* {
    transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}

Happy coding :)