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)