ASP.NET Core 2.2 - AJAX Get And Post Methods

Ken Haggerty
Created 03/06/2019 - Updated 03/26/2019 19:02

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.

From Learn Razor Pages. Handler Methods in Razor Pages

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.

From Bootstrap Documentation. Spinners

Build, run and test.

None Selected:
Get Select Log Type
Success:
Get Success
Failed:
Get 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:
Update Select Log Type
Success:
Update Success
Failed:
Update 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.");
}
Throw Exception:
Get Exception

Comment Count = 0

Please log in to comment.

Login Register
Logged in users receive web notifications.
Web Notifications