ASP.NET Core 2.2 - AJAX Get And Post Methods
While creating this article, I took a hard look at whether I wanted to continue to develop with jQuery. The 2 biggest considerations were Bootstrap components and client-side validation. For the demonstration in this article, I use a Bootstrap Modal which relies on jQuery, but I developed the AJAX calls with XML HttpRequest. I use a radio button group for the item selection and demonstrate the vanilla JavaScript methods to manipulate the values.
I will assume you have created a new ASP.NET Core 2.2 Razor Pages project. I reference my previous article ASP.NET Core 2.2 - Error and Exception Handling but I will show an alternate error handling. I use page handlers for the server-side code and mock an item not found error. The Get and Post methods have very different implementations.
Let's start with a Get method. You can use the default Index page for testing.
Edit Index.cshtml.cs, add OnGetLogTypeById:
public JsonResult OnGetLogTypeById(int logTypeId) { var returnString = "Not Found"; switch (logTypeId) { case 1: returnString = "Debug"; break; case 2: returnString = "Information"; break; case 3: returnString = "Warning"; break; case 4: returnString = "Error"; break; case 5: returnString = "Critical"; break; default: break; } if (returnString == "Not Found") { return new JsonResult(new { Status = "Failed", Result = "Type Not Found" }); //throw new InvalidOperationException($"OnGetLogTypeId - Id Not Found."); } return new JsonResult(new { Status = "Success", Result = returnString }); }
Notice the JsonResult return type and the commented exception which I will explain later.
Edit Index.cshtml, add the radio buttons and Bootstrap modal:
<div class="row mb-2"> <div class="custom-control custom-radio custom-control-inline"> <input type="radio" id="DebugRadioButtonId" value="1" class="custom-control-input" name="LogTypeGroup" /> <label class="custom-control-label" for="DebugRadioButtonId"> Debug </label> </div> <div class="custom-control custom-radio custom-control-inline"> <input type="radio" id="InformationRadioButtonId" value="2" class="custom-control-input" name="LogTypeGroup" /> <label class="custom-control-label" for="InformationRadioButtonId"> Information </label> </div> <div class="custom-control custom-radio custom-control-inline"> <input type="radio" id="WarningRadioButtonId" value="3" class="custom-control-input" name="LogTypeGroup" /> <label class="custom-control-label" for="WarningRadioButtonId"> Warning </label> </div> <div class="custom-control custom-radio custom-control-inline"> <input type="radio" id="ErrorRadioButtonId" value="4" class="custom-control-input" name="LogTypeGroup" /> <label class="custom-control-label" for="ErrorRadioButtonId"> Error </label> </div> <div class="custom-control custom-radio custom-control-inline"> <input type="radio" id="CriticalRadioButtonId" value="5" class="custom-control-input" name="LogTypeGroup" /> <label class="custom-control-label" for="CriticalRadioButtonId"> Critical </label> </div> <div class="custom-control custom-radio custom-control-inline"> <input type="radio" id="NotFoundRadioButtonId" value="6" class="custom-control-input" name="LogTypeGroup" /> <label class="custom-control-label" for="NotFoundRadioButtonId"> Not Found </label> </div> </div> <div class="row mb-2"> <button type="button" id="GetLogTypeButtonId" class="btn btn-primary">Get Log Type</button> </div> <div class="modal" tabindex="-1" role="dialog"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title"></h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary btn-update">Update</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button> </div> </div> </div> </div>
Edit Index.cshtml, add the JavaScript:
@section Scripts { <script> function getLogTypeById(id) { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { var modalBody = document.querySelector('.modal-body'); if (this.readyState == 4 && this.status == 200) { modalBody.innerHTML = ''; var responseJSON = JSON.parse(this.responseText); if (responseJSON.status == 'Success') { var dl = document.createElement('dl'); for (prop in responseJSON) { var dt = document.createElement('dt'); dt.textContent = prop; dl.appendChild(dt); var dd = document.createElement('dd'); dd.textContent = responseJSON[prop]; dl.appendChild(dd); } var successDiv = document.createElement('div'); successDiv.classList.add('alert', 'alert-success'); successDiv.appendChild(dl); modalBody.appendChild(successDiv); } else { var dl = document.createElement('dl'); for (prop in responseJSON) { var dt = document.createElement('dt'); dt.textContent = prop; dl.appendChild(dt); var dd = document.createElement('dd'); dd.textContent = responseJSON[prop]; dl.appendChild(dd); } var alertDiv = document.createElement('div'); alertDiv.classList.add('alert', 'alert-danger'); alertDiv.appendChild(dl); modalBody.appendChild(alertDiv); } } else if (this.readyState == 4) { modalBody.innerHTML = ''; var alertDiv = document.createElement('div'); alertDiv.classList.add('alert', 'alert-danger'); alertDiv.textContent = 'Error ' + this.status; modalBody.appendChild(alertDiv); } }; xhr.open('GET', '/index?handler=LogTypeById&logTypeId=' + id); xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); xhr.onerror = function (error) { var modalBody = document.querySelector('.modal-body'); modalBody.innerHTML = ''; var alertDiv = document.createElement('div'); alertDiv.classList.add('alert', 'alert-danger'); alertDiv.textContent = 'Error ' + error.target.status; modalBody.appendChild(alertDiv); }; xhr.send(); } document.addEventListener('DOMContentLoaded', function () { document.querySelector('#GetLogTypeButtonId').addEventListener('click', function () { var modalBody = document.querySelector('.modal-body'); modalBody.innerHTML = ''; document.querySelector('.modal-title').textContent = 'Get Log Type'; document.querySelector('.btn-update').style.display = 'none'; var radioButton = document.querySelector('input[name="LogTypeGroup"]:checked'); if (radioButton == null) { var alertDiv = document.createElement('div'); alertDiv.classList.add('alert', 'alert-danger'); alertDiv.textContent = 'Please select a log type.'; modalBody.appendChild(alertDiv); $('.modal').modal('show'); return; } // Begin Spinner - Bootstrap 4.2.1 or above //var spinnerDiv = document.createElement('div'); //spinnerDiv.classList.add('spinner-border', 'text-alert', 'd-flex', 'mt-2', 'ml-auto', 'mr-auto'); //spinnerDiv.setAttribute('role', 'status'); //var span = document.createElement('span'); //span.classList.add('sr-only'); //span.innerText = 'Loading...'; //spinnerDiv.appendChild(span); //modalBody.appendChild(spinnerDiv); // End Spinner $('.modal').modal('show'); var id = parseInt(radioButton.value); getLogTypeById(id); }); }); </script> }
Typically, a Get method passes an id parameter. I just appended the id variable to the xhr.open's URL. You should use a loading spinner while waiting for a response. Bootstrap 4.2.1 introduced a spinner component.
Build, run and test.
None Selected:
Success:
Failed:
Now let's add a Post method.
Edit Index.cshtml.cs, add OnPostUpdateLogTypeById:
public JsonResult OnPostUpdateLogTypeById([FromBody] UpdateLogTypeModel model) { if (model == null) { return new JsonResult(new { Status = "Failed", Error = "model = null" }); //throw new InvalidOperationException($"OnPostUpdateLogTypeById - model = null."); } // Find log type var validIds = new List(new[] { 1, 2, 3, 4, 5 }); if (!validIds.Contains(model.LogTypeId)) { return new JsonResult(new { Status = "Failed", model.LogTypeId, Error = "Id Not Found" }); //throw new InvalidOperationException($"OnPostUpdateLogTypeById - Id Not Found."); } // Update log type = model.LogTypeValue var updateSuccess = true; if (!updateSuccess) { return new JsonResult(new { Status = "Failed", model.LogTypeId, Error = "Update Error" }); //throw new InvalidOperationException($"OnPostUpdateLogTypeById - Update Error."); } return new JsonResult(new { Status = "Success", model.LogTypeId, model.LogTypeValue }); }
Notice the [FromBody] decoration and UpdateLogTypeModel class.
Edit Index.cshtml.cs, add the UpdateLogTypeModel class:
public class UpdateLogTypeModel { public string Status { get; set; } public int LogTypeId { get; set; } public string LogTypeValue { get; set; } }
Edit Index.cshtml, add the UpdateLogTypeButton and an empty post form above the modal:
<div class="row"> <form method="post"></form> <button type="button" id="UpdateLogTypeButtonId" class="btn btn-primary">Update Log Type</button> </div>
We will use the empty form to get a RequestVerificationToken.
Edit Index.cshtml, add the updateLogTypeById function to JavaScript:
function updateLogTypeById(logTypeId, logTypeValue) { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { var modalBody = document.querySelector('.modal-body'); if (this.readyState == 4 && this.status == 200) { var responseJSON = JSON.parse(this.responseText); if (responseJSON.status == 'Success') { var radioButton = document.querySelector('input[name="LogTypeGroup"][value="' + responseJSON.logTypeId + '"]'); var radioButtonId = radioButton.getAttribute('id'); document.querySelector('label[for="' + radioButtonId + '"]').textContent = responseJSON.logTypeValue; } else { modalBody.innerHTML = ''; document.querySelector('.btn-update').style.display = 'none'; var dl = document.createElement('dl'); for (prop in responseJSON) { var dt = document.createElement('dt'); dt.textContent = prop; dl.appendChild(dt); var dd = document.createElement('dd'); dd.textContent = responseJSON[prop]; dl.appendChild(dd); } var alertDiv = document.createElement('div'); alertDiv.classList.add('alert', 'alert-danger'); alertDiv.appendChild(dl); modalBody.appendChild(alertDiv); $('.modal').modal('show'); } } else if (this.readyState == 4) { var modalBody = document.querySelector('.modal-body'); modalBody.innerHTML = ''; document.querySelector('.btn-update').style.display = 'none'; var alertDiv = document.createElement('div'); alertDiv.classList.add('alert', 'alert-danger'); alertDiv.textContent = 'Error ' + this.status; modalBody.appendChild(alertDiv); $('.modal').modal('show'); } }; xhr.open('POST', '/index?handler=UpdateLogTypeById'); xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); xhr.setRequestHeader('RequestVerificationToken', document.querySelector('input[type="hidden"][name="__RequestVerificationToken"]').value); xhr.onerror = function (error) { var modalBody = document.querySelector('.modal-body'); modalBody.innerHTML = ''; var alertDiv = document.createElement('div'); alertDiv.classList.add('alert', 'alert-danger'); alertDiv.textContent = 'Error ' + error.target.status; modalBody.appendChild(alertDiv); document.querySelector('.btn-update').style.display = 'none'; $('.modal').modal('show'); }; xhr.send(JSON.stringify({ logTypeId: logTypeId, logTypeValue: logTypeValue })); }
Edit Index.cshtml, add the 2 button event listeners after DOMContentLoaded in JavaScript:
document.querySelector('#UpdateLogTypeButtonId').addEventListener('click', function () { var modalBody = document.querySelector('.modal-body'); modalBody.innerHTML = ''; document.querySelector('.modal-title').textContent = 'Update Log Type'; document.querySelector('.btn-update').style.display = 'inline-block'; var radioButton = document.querySelector('input[name="LogTypeGroup"]:checked'); if (radioButton == null) { var alertDiv = document.createElement('div'); alertDiv.classList.add('alert', 'alert-danger'); alertDiv.textContent = 'Please select a log type.'; modalBody.appendChild(alertDiv); document.querySelector('.btn-update').style.display = 'none'; $('.modal').modal('show'); return; } var radioButtonId = radioButton.getAttribute('id'); var radioButtonText = document.querySelector('label[for="' + radioButtonId + '"]').textContent.trim(); var hiddenInput = document.createElement('input'); hiddenInput.setAttribute('type', 'hidden'); hiddenInput.setAttribute('id', 'LogTypeIdHidden'); hiddenInput.value = radioButton.value; modalBody.appendChild(hiddenInput); var header = document.createElement('h6'); header.textContent = radioButtonText; modalBody.appendChild(header); var textInput = document.createElement('input'); textInput.setAttribute('type', 'text'); textInput.setAttribute('id', 'LogTypeInputId'); textInput.classList.add('form-control'); modalBody.appendChild(textInput); $('.modal').modal('show'); }); document.querySelector('.btn-update').addEventListener('click', function () { var logTypeId = document.querySelector('#LogTypeIdHidden').value; var logTypeValue = document.querySelector('#LogTypeInputId').value; if (logTypeValue == '') { if (!document.querySelector('#ErrorLabelId')) { var errorLabel = document.createElement('label'); errorLabel.setAttribute('id', 'ErrorLabelId'); errorLabel.classList.add('text-danger'); errorLabel.textContent = 'New value is required.'; document.querySelector('.modal-body').appendChild(errorLabel); } return; } var id = parseInt(logTypeId); updateLogTypeById(id, logTypeValue); $('.modal').modal('hide'); });
Build, run and test.
None Selected:
Success:
Failed:
I was surprised when I threw an exception for the not found error, the xhr.onerror function was not called. Instead the ReadyState when to 4 but the Status was 500 not 200. You wouldn't want to throw exceptions unless you have implemented proper error and exception handling (see ASP.NET Core 2.2 - Error and Exception Handling). If you edit Index.cshtml.cs > OnGetLogTypeId to throw an exception, you see why I used else if (this.readyState == 4) in the getLogTypeById JavaScript function.
Edit Index.cshtml.cs > OnGetLogTypeById, throw Exception on Not Found:
if (returnString == "Not Found") { //return new JsonResult(new { Status = "Failed", Result = "Type Not Found" }); throw new InvalidOperationException($"OnGetLogTypeId - Id Not Found."); }
Comments(0)