Simple Delete Confirmation in ASP.NET MVC

In this post, I will present a simple implementation of the Action-Confirm Design Pattern in ASP.NET MVC 5.

Deleting an Entity

Consider a simple listing of people in a grid as created by right clicking on the Controllers folder, selecting Add –> Controller, then selecting MVC Controller with views, using Entity Framework.

When we view the list of people, we see a Delete link for each row.

When we click Delete, we are taken to a Delete confirmation page.

The user experience here is acceptable, but in my opinion, navigating to a new page breaks the user’s flow a little too much. I think the same would be true if we used a modal dialog instead.

Action-Confirm Design Pattern

Lately, I have been using the Action-Confirm Design Pattern for entity delete actions in my applications. This pattern offers a less intrusive option than a traditional confirmation dialog / confirmation page.

When the user clicks the Delete icon [or link] it transforms into a Confirm button that looks distinctively different. The user can now either click the Confirm button to proceed with the action or click outside it (or press Esc) to revert to the previous state.

Using this pattern, when we click the Delete link, the delete link is replaced with a Confirm Delete button. If the user does not want to delete the item, they can press escape or click anywhere else on the document to cancel the action. When an item is successfully deleted, the row fades out and is removed from the page.

Here is the new Index.cshtml file that accomplishes this.

@model IEnumerable<ActionConfirmSample.Models.Person>

@{
ViewBag.Title = "Index";
}

<style>
.delete-section {
display: inline;
}

</style>


<h2>Index</h2>
@Html.AntiForgeryToken()
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.FirstName)
</th>
<th>
@Html.DisplayNameFor(model => model.LastName)
</th>
<th></th>
</tr>

@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.FirstName)
</td>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
@Html.ActionLink("Details", "Details", new { id=item.Id }) |
<div class="delete-section" >
<a class="delete-link" href="@Url.Action("Delete", new {id = item.Id})">Delete</a>
<div class="btn btn-primary delete-confirm" style="display:none" data-delete-id="@item.Id">Confirm Delete</div>
</div>
</td>
</tr>
}

</table>

@section scripts{
<script type="text/javascript">
$(function () {
$("a.delete-link").click(function () {
var deleteLink = $(this);
deleteLink.hide();
var confirmButton = deleteLink.siblings(".delete-confirm");
confirmButton.show();

var cancelDelete = function () {
removeEvents();
showDeleteLink();
};

var deleteItem = function () {
removeEvents();
confirmButton.hide();
$.post(
'@Url.Action("Delete")',
AddAntiForgeryToken({ id: confirmButton.attr('data-delete-id') }))
.done(function () {
var parentRow = deleteLink.parents("tr:first");
parentRow.fadeOut('fast', function () {
parentRow.remove();
});
}).fail(function (data) {
alert("error");
});
return false;
};

var removeEvents = function () {
confirmButton.off("click", deleteItem);
$(document).on("click", cancelDelete);
$(document).off("keypress", onKeyPress);
};

var showDeleteLink = function () {
confirmButton.hide();
deleteLink.show();
};

var onKeyPress = function (e) {
//Cancel if escape key pressed
if (e.which == 27) {
cancelDelete();
}
};

confirmButton.on("click", deleteItem);
$(document).on("click", cancelDelete);
$(document).on("keypress", onKeyPress);

return false;
});

AddAntiForgeryToken = function (data) {
data.__RequestVerificationToken = $('input[name=__RequestVerificationToken]').val();
return data;
};
});
</script>

}