Skip to content
Nubgrammer
Menu
  • Home
  • Contact Me
Menu

AJAX-based In-place Record Editing in .Net (with demo)

Posted on March 19, 2019March 19, 2019 by Tyler Sells

In-place record editing with AJAX has been a thorn in my side for a long time. I’ve got it figured out these days, but that wasn’t always the case. I wrote a post on this topic before I knew what I was doing and it’s time to set the record straight. @Html.Raw exposes many XSS-scripting security concerns and there was no need to use it for my intended purpose at that time. Anyway, let’s get to the fun stuff.

The Demo

It’s a little rough, but you get the gist of it. Unfortunately, dotNetFiddle doesn’t provide the greatest code-sharing experience so I’ll break it down a little bit.

The Controller and Model

public class HomeController : Controller
{
    [HttpGet]
    public ActionResult Index()
    {
        return View(GetUsers());
    }

    public List<UserViewModel> GetUsers()
    {
        //faux data from db
        var list = new List<UserViewModel>();
        for (int i = 0; i < 10; i++)
        {
            list.Add(new UserViewModel()
            {Id = i, Username = "User_" + i, FirstName = "UserFirst " + i, LastName = "UserLast " + i});
        }

        return list;
    }

    [HttpPost]
    public JsonResult Delete(int userId)
    {
        //perform deletion here
        /***************/
        return Json("I deleted " + userId);
    }

    [HttpPost]
    public JsonResult Edit(UserViewModel user)
    {
        //save data here
        /***************/
        //then return updated data as json
        return Json(user);
    }
}
public class UserViewModel
{
    public int Id
    {
        get;
        set;
    }

    public string Username
    {
        get;
        set;
    }

    public string FirstName
    {
        get;
        set;
    }

    public string LastName
    {
        get;
        set;
    }
}

The controller and model are the easiest parts of the whole thing. I left out the actual database logic but as you can see, there is the Index action which returns the main view, the Delete action which handles deleting the record, and the Edit action which handles the changes.

The View

Let’s look at the important parts of the view:

<div class="container">
    <!-- Create the edit form in your controller -->
    <form id="edit">
        <div class="row">
             <input type="hidden" id="userId" name="Id">
             <div class="col-md-2">
                <label for="username">Username:</label>
                <input type="text" id="username" name="Username" class="form-control">
             </div>
             <div class="col-md-2">
                <label for="firstname">First Name:</label>
                <input type="text" id="firstname" name="FirstName" class="form-control">
             </div>
             <div class="col-md-2">
                <label for="lastname">Last Name:</label>
                <input type="text" id="lastname" name="LastName" class="form-control">
            </div>

        </div>
        <input type="submit" class="btn btn-info" value="Save">
    </form>
    <!-- Display your users as usual -->
    <table class="table">
        
        @foreach(var item in @Model){
            <tr>
                <td>
                    @item.Username
                </td>
                <td>
                    @item.FirstName
                </td>
                <td>
                    @item.LastName
                </td>
                <td>
                    <!-- Create dummy buttons to handle edit/delete -->
                    <a href="#" class="btn btn-success edit-btn" data-id=@item.Id>Edit</a>|
                    <a href="#" class="btn btn-danger delete-btn" data-id=@item.Id>Delete</a>
                </td>
            </tr>
        }
    </table>
</div>

The trick to in-place record editing with AJAX is forgetting about the in-place part. What I mean is that at its roots, we are just moving around a simple edit form. We show it when we want to use it and hide it when we don’t.

What I’ve done in the html above is create a fairly basic form to represent the c# view model class and simply listed all the users that were passed from the controller. I’ve also added a dummy link for the edit and delete buttons that contain data-attribute references to the id of each respective user. In many cases, with a bit more work, this would be an acceptable user experience without the in-place stuff. But where’s the fun in that?

The JS/jQuery

You guessed it, the javascript is where the magic happens:

const deleteBtns = document.querySelectorAll(".delete-btn");
deleteBtns.forEach((btn)=>{
    // Listen for clicks on your delete buttons, then send an ajax call back to your controller
    // for the Delete method, passing the id data-attribute to identify the record
    btn.addEventListener("click",function(){
        $.ajax({
            url: '@Url.RouteUrl(new{ action="Delete", controller="Home"})',
            data: {userId:parseInt(btn.dataset.id,10)},
            type: 'POST',
            dataType:"json",
            complete: function(resp) {
                alert(resp.responseText);
            }
        });
        //Hide (or delete) the row after a deletion is done to update the UI
        removeRow(btn);
    })
})
removeRow = (elemInRow)=>{
    $(elemInRow).closest("tr").hide();	
}
const form = document.getElementById("edit");
// Hide the form initially
$(form).hide();
const editBtns = document.querySelectorAll(".edit-btn");
editBtns.forEach((btn)=>{
    // When an edit button is clicked, set the userId input field of the form
    // to point to the id of the record selected.
    // Then move the form into the same row.
    // Lastly, show the form.
    btn.addEventListener("click",function(){
        document.getElementById("userId").value=btn.dataset.id;
        $(form).appendTo($(btn).closest("td"));
        $(form).show();
    })
})
// Handle the form submit by passing it's input to the Edit function. 
// Update the UI to reflect the changes.
// Hide the form again and send feedback via an alert.
form.addEventListener("submit",function(event){
    event.preventDefault();
    $.ajax({
        url: '@Url.RouteUrl(new{ action="Edit", controller="Home"})',
        data: $(this).serialize(),
        type: 'POST',
        dataType: 'json',
        complete: function(resp) {
            const jsonOb = JSON.parse(resp.responseText);
            updateRow(jsonOb.Username,jsonOb.FirstName,jsonOb.LastName,$(form).closest("tr")[0]);
            $(form).hide();
            alert(`I saved: ${JSON.stringify(jsonOb)}`);
        }
    });
})
updateRow = (username, firstname, lastname,row)=>{
    const tds = $("td",row);
    $(tds[0]).text(username);
    $(tds[1]).text(firstname);
    $(tds[2]).text(lastname);
}

Breaking it Down

Hooking up the delete functionality is pretty straightforward:

const deleteBtns = document.querySelectorAll(".delete-btn");
deleteBtns.forEach((btn)=>{
    // Listen for clicks on your delete buttons, then send an ajax call back to your controller
    // for the Delete method, passing the id data-attribute to identify the record
    btn.addEventListener("click",function(){
        $.ajax({
            url: '@Url.RouteUrl(new{ action="Delete", controller="Home"})',
            data: {userId:parseInt(btn.dataset.id,10)},
            type: 'POST',
            dataType:"json",
            complete: function(resp) {
                alert(resp.responseText);
            }
        });
        //Hide (or delete) the row after a deletion is done to update the UI
        removeRow(btn);
    })
})
removeRow = (elemInRow)=>{
    $(elemInRow).closest("tr").hide();	
}

We just need to loop through the buttons and watch for clicks. We will then fire off an ajax call with the appropriate user ID to the Delete function in the controller. Then we will use a little bit of jQuery to hide the row in the UI.

The next block of code handles part of the editing functionality:

const form = document.getElementById("edit");
// Hide the form initially
$(form).hide();
const editBtns = document.querySelectorAll(".edit-btn");
editBtns.forEach((btn)=>{
    // When an edit button is clicked, set the userId input field of the form
    // to point to the id of the record selected.
    // Then move the form into the same row.
    // Lastly, show the form.
    btn.addEventListener("click",function(){
        document.getElementById("userId").value=btn.dataset.id;
        $(form).appendTo($(btn).closest("td"));
        $(form).show();
    })
})

We want the hide the form initially and then set up each of the edit buttons to set the userId input element of the form to the correct user ID. Finally, we will move the form to the proper table cell and show it.

The last section is the form submit section:

// Handle the form submit by passing it's input to the Edit function. 
// Update the UI to reflect the changes.
// Hide the form again and send feedback via an alert.
form.addEventListener("submit",function(event){
    event.preventDefault();
    $.ajax({
        url: '@Url.RouteUrl(new{ action="Edit", controller="Home"})',
        data: $(this).serialize(),
        type: 'POST',
        dataType: 'json',
        complete: function(resp) {
            const jsonOb = JSON.parse(resp.responseText);
            updateRow(jsonOb.Username,jsonOb.FirstName,jsonOb.LastName,$(form).closest("tr")[0]);
            $(form).hide();
            alert(`I saved: ${JSON.stringify(jsonOb)}`);
        }
    });
})
updateRow = (username, firstname, lastname,row)=>{
    const tds = $("td",row);
    $(tds[0]).text(username);
    $(tds[1]).text(firstname);
    $(tds[2]).text(lastname);
}

We want to hi-jack the default submit behavior of the form and send it as an ajax request instead. Once we submit the changed data, then we need to update the UI.

In Conclusion…

There you have it, a quick and simple way to implement in-place ajax-based record editing. I wanted to post this because I’ve learned a lot since my original post. At the time, I was trying to store a lot of unnecessary data in html attributes and the use of @Html.Raw is known to be a huge security concern. This can be adapted to just about any front-end application with a few modifications.

Disclaimer: This project is meant to be a working demo and NOT secure code. You should use AntiForgeryTokens for all form submits and I would even recommend putting the delete-related code in it’s own form instead of a regular link (in order to add an AntiForgeryToken and conform to best-practices).

Author: Tyler Sells

Github

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • More
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to email a link to a friend (Opens in new window) Email
  • Click to share on Reddit (Opens in new window) Reddit

Like this:

Like Loading...

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Follow me on Twitter

My Tweets

Github Repos

vtsells (Tyler Sells)

Tyler Sells

vtsells
http://www.nubgrammer.com
Joined on Jun 21, 2017
9 Public Repositories
100DaysOfCode
embers
MultiSelect
MVC-Project-Start
nubgrammer.com
PermIT
Spray
vtsells.github.io
Wizard
0 Public Gists

Categories

  • #100DaysOfCode (4)
  • ASP.NET (7)
  • ASP.NET Core (1)
  • ASP.NET MVC (3)
  • CSS (4)
  • General (13)
  • JS (3)
  • LESS (2)
  • Snippets (4)
  • Tools (4)
  • Tutorials (9)

Recent Posts

  • Creating a Knockout.js project on Codepen
  • 100DaysOfCode Day 3 – A State of Mind
  • 100DaysOfCode Day 2 – The Building Blocks
  • 100DaysOfCode Day 1 (Sort of cheated already)
  • Committing to #100DaysOfCode
© 2025 Nubgrammer | Powered by Superbs Personal Blog theme
We use cookies on our website to give you the most relevant experience by remembering your preferences and repeat visits. By clicking “Accept”, you consent to the use of ALL the cookies.
Do not sell my personal information.
Cookie SettingsAccept
Manage consent

Privacy Overview

This website uses cookies to improve your experience while you navigate through the website. Out of these, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may affect your browsing experience.
Necessary
Always Enabled
Necessary cookies are absolutely essential for the website to function properly. These cookies ensure basic functionalities and security features of the website, anonymously.
CookieDurationDescription
cookielawinfo-checkbox-analytics11 monthsThis cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics".
cookielawinfo-checkbox-functional11 monthsThe cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional".
cookielawinfo-checkbox-necessary11 monthsThis cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary".
cookielawinfo-checkbox-others11 monthsThis cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other.
cookielawinfo-checkbox-performance11 monthsThis cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance".
viewed_cookie_policy11 monthsThe cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data.
Functional
Functional cookies help to perform certain functionalities like sharing the content of the website on social media platforms, collect feedbacks, and other third-party features.
Performance
Performance cookies are used to understand and analyze the key performance indexes of the website which helps in delivering a better user experience for the visitors.
Analytics
Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics the number of visitors, bounce rate, traffic source, etc.
Advertisement
Advertisement cookies are used to provide visitors with relevant ads and marketing campaigns. These cookies track visitors across websites and collect information to provide customized ads.
Others
Other uncategorized cookies are those that are being analyzed and have not been classified into a category as yet.
SAVE & ACCEPT
%d

    Privacy Policy