When you are writing a .NET Core application then Visual Studio allows you to add the controller scaffolding once you have defined the data items in your model. As part of the controller it also can also define some .cshtml pages to handle the CRUD operations (Create, Retrieve, Update and Delete). If you are simply putting together something quick, or a backoffice application there is very little else to do, unless of course you want the application to look ‘pretty’.
Once the controller is added you will get a default index.cshtml page that looks something like this.
After a few changes with CSS, and adding some extras like a search box, paging, record counts,sortable columns and a constant number of rows in the grid it looks something like this
In the original the link to actions like delete a record will take the user to a separate delete.cshtml page.
I always feel that something like this is much better as an embedded dialog and AJAX call instead of jumping between pages. It always gives the user a better experience in my opinion as pages aren’t constantly being loaded each time they do something new.
So the desired result should look something like this:
As the default framework for .NET Core is Bootstrap the tools are all ready available to get rid of the delete.cshtml page and use a modal dialog instead for a much cleaner flow through the application. The modal dialog prevents users from doing anything in the background page.
First we need to make some changes to the relevant existing index.cshtml page.
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<input type="hidden" id="deleteRecord" name="deleteRecord" value="-" />
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Delete</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body" id="deleteBody">
...
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-danger" id="deleteButton" onclick="deleteSite();">Delete Record</button>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="errorModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">An error has occured</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div id="errorBody" class="modal-body">
...
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
Firstly add the code for two dialogs to the end of the page.
I have included two dialogs as one will be for the actual delete question, and the other will be used to display any error messages during the delete action and I have kept it separate just in case I want to style the error message dialog separately.
Next I need to handle the Anti Forgery token that the delete request must include otherwise the operation will fail. The simplest way to do that is to create an extension method on HttpContext. It doesn’t have to be an extension method but it makes life a lot simpler and can be easily reused in any other AJAX functions on other pages.
public static class HttpContextExtensions
{
public static string GetAntiforgeryToken(this HttpContext httpContext)
{
var antiforgery = (IAntiforgery)httpContext.RequestServices.GetService(typeof(IAntiforgery));
var tokens = antiforgery.GetAndStoreTokens(httpContext);
return tokens.RequestToken;
}
}
There should be an Extensions folder in the project, and the code can be added there. If there is no extensions folder then it makes sense to create one. If you have never used extension methods before more information can be found here.
Next back in index.cshtml we need to add some JavaScript to the bottom of the page to actually make things happen. To make life easier I am using jQuery but if you prefer ‘old school’ JavaScript or another library then it will work just as well. If you are using jQuery then ensure the libraries are being loaded in the _Layout.cshtml file or other relevant locations.
<script type="text/javascript">
function showModal(text, record) {
$('#deleteModal').find("#deleteBody").html("Are you sure you wish to delete <strong>" + text + "</strong>");
$('#deleteRecord').attr("value",record);
$('#deleteModal').modal();
}
function deleteSite() {
$.ajax({
type: "POST",
url: "@Url.Action("Delete")/"+$("#deleteRecord").val(),
data: {id: $("#deleteRecord").val() },
headers: { "RequestVerificationToken": "@Context.GetAntiforgeryToken()" },
success: function (msg) {
$('#deleteModal').modal('hide');
$('#reload').html(msg);
},
error: function (req, status, error) {
$('#deleteModal').modal('hide');
if (req.status != 200) {
$('#deleteModal').on('hidden.bs.modal', function (e) {
$('#errorModal').modal();
});
}
}
});
}
</script>
There are two functions to be added, the second deleteSite() uses jQuery to make an AJAX call to the Delete function in the controller. The function adds the header RequestVerficationToken with the returned value from our extension method GetAntiforgeryToken. Without this the call will fail.
The function also includes the id of the record to be deleted.
The results of the function on success returns the page data from the Delete function and reloads the page, showing the list without the deleted item.
If the call fails then it will display the error message dialog. This could be expanded to display a more detailed error message in a similar way to the delete dialog by directly modifying the document structure within the dialog.
The first function showModal(text, record) is the JavaScript call that will be called from a link in the grid, and it will take a text parameter to display in the body of the dialog “Are you sure you wish to delete <text>” and also the id of the record that will be deleted. These are provided as attributes of the model. Also inside the dialog code I have included a hidden field called deleteRecord that the first JavaScript function updates and is then used in turn by the AJAX call to the delete function..
<input type="hidden" id="deleteRecord" name="deleteRecord" value="-" />
To link to this function we will modify the line in Index.cshtml where the data gets listed in the grid. Change this:
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
So that it becomes:
<a onclick="showModal('@item.SiteName','@item.Id');"><img alt="Delete" title="Delete" src="/images/GridTrash.png" /></a>
In this example I have replaced the word Delete with a small graphic of a trash can but the exact format can be easily modified to be text or a graphic or both. The code calls the new JavaScript function showModal(text, record) explicitly.
That is all that is required to make the program flow better, and the dialogs can be modified or extended to show more data, or even replaced with something else completely.
Hopefully it has provided some food for thought.