ASP.NET Core 2.2 - Fetch And Promise

Ken Haggerty
Created 05/03/2019 - Updated 05/03/2019 21:52

This article will demonstrate the detection and use of the JavaScript fetch function. I will assume you have created a new ASP.NET Core 2.2 Razor Pages project. I won't use Identity or Individual User Accounts.

I am researching Progressive Web Applications which rely on promises. The fetch function is also dependent on promise. From MDN - "The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value." Promises are supported by most modern browsers but not all. There are small polyfills which add fetch and promise support. I will use IE 11 to detect and mitigate the lack of fetch and promise features. This demonstration will use a slightly modified example from AJAX Get And Post Methods. The JavaScript will use BootstrapNative if found.

Edit Index.cshtml.cs, add a LogTypeById page handler:
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 });
}
Edit Index.cshtml, add radio buttons and modal:
<div class="row mb-2">
    <div class="col-12">
        <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>
<div class="row">
    <div class="col-12">
        <button type="button" id="GetLogTypeButtonId" class="btn btn-primary">Get Log Type</button>
    </div>
</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">AJAX Get and Post Modal</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-secondary" data-dismiss="modal">Cancel</button>
            </div>
        </div>
    </div>
</div>

While composing this article, I discovered IE 11 will only accept the first class if you attempt to add multiple classes with element.classList.add().

Edit Index.cshtml, add the JavaScript:
@section Scripts {

    <script>

        var modalInstance;
        // BootstrapNative
        if (window.BSN) {
            modalInstance = new Modal(document.querySelector('.modal'));
        }
        var modalBody = document.querySelector('.modal-body');

        function getLogTypeById(id) {
            var xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function () {
                if (this.readyState == 4 && this.status == 200) {
                    modalBody.innerHTML = '';
                    if (this.responseText.substring(0, 15) == '<!DOCTYPE html>') {
                        var alertDiv = document.createElement('div');
                        alertDiv.classList.add('alert');
                        alertDiv.classList.add('alert-danger');
                        alertDiv.textContent = 'LogTypeById Method Not Found';
                        modalBody.appendChild(alertDiv);
                    }
                    else {
                        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');
                            successDiv.classList.add('alert-success');
                            successDiv.appendChild(dl);
                            modalBody.appendChild(successDiv);
                        }
                        else {
                            modalBody.innerHTML = '';
                            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');
                            alertDiv.classList.add('alert-danger');
                            alertDiv.appendChild(dl);
                            modalBody.appendChild(alertDiv);
                        }
                    }
                }
                else if (this.readyState == 4) {
                    modalBody.innerHTML = '';
                    var alertDiv = document.createElement('div');
                    alertDiv.classList.add('alert');
                    alertDiv.classList.add('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) {
                modalBody.innerHTML = '';
                var alertDiv = document.createElement('div');
                alertDiv.classList.add('alert');
                alertDiv.classList.add('alert-danger');
                alertDiv.textContent = 'Error ' + error.target.status;
                modalBody.appendChild(alertDiv);
            };
            xhr.send();
        }

        document.addEventListener('DOMContentLoaded', function () {
            document.querySelector('#GetLogTypeButtonId').addEventListener('click', function () {
                modalBody.innerHTML = '';
                document.querySelector('.modal-title').textContent = 'Get Log Type';
                var radioButton = document.querySelector('input[name="LogTypeGroup"]:checked');
                if (radioButton == null) {
                    var alertDiv = document.createElement('div');
                    alertDiv.classList.add('alert');
                    alertDiv.classList.add('alert-danger');
                    alertDiv.textContent = 'Please select a log type.';
                    modalBody.appendChild(alertDiv);
                    // BootstrapNative
                    if (window.BSN) {
                        modalInstance.show();
                    }
                    else {
                        $('.modal').modal('show');
                    }
                    return;
                }
                // Begin Spinner - Bootstrap 4.2.1 or above
                var spinnerDiv = document.createElement('div');
                spinnerDiv.classList.add('spinner-border');
                spinnerDiv.classList.add('text-alert');
                spinnerDiv.classList.add('d-flex');
                spinnerDiv.classList.add('mt-2');
                spinnerDiv.classList.add('ml-auto');
                spinnerDiv.classList.add('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

                // BootstrapNative
                if (window.BSN) {
                    modalInstance.show();
                }
                else {
                    $('.modal').modal('show');
                }
                var id = parseInt(radioButton.value);
                getLogTypeById(id);

            }, false);

        }, false);

    </script>
}

Build, run and test. If all looks good, verify function with IE 11. If you have VS2017 || VS2019 && IE 11 installed, you can debug with IE 11.

VS Debug IE

XMLHttpRequest works with IE 11.

IE AJAX Success

I have converted the XMLHttpRequest method to a fetch method.

Edit Index.cshtml, add the fetchLogTypeById function to the JavaScript:
function fetchLogTypeById(id) {

    return fetch('/index?handler=LogTypeById&logTypeId=' + id,
        {
            method: 'get',
            headers: {
                'Content-Type': 'application/json;charset=UTF-8'
            }
        })
        .then(function (response) {
            if (response.ok) {
                return response.text();
            } else {
                throw Error('LogTypeById Response Not OK');
            }
        })
        .then(function (text) {
            try {
                return JSON.parse(text);
            } catch (err) {
                throw Error('LogTypeById Method Not Found');
            }
        })
        .then(function (responseJSON) {
            modalBody.innerHTML = '';
            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');
                successDiv.classList.add('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');
                alertDiv.classList.add('alert-danger');
                alertDiv.appendChild(dl);
                modalBody.appendChild(alertDiv);
            }
        })
        .catch(function (error) {
            modalBody.innerHTML = '';
            var alertDiv = document.createElement('div');
            alertDiv.classList.add('alert');
            alertDiv.classList.add('alert-danger');
            alertDiv.textContent = 'LogTypeById Catch = ' + error;
            modalBody.appendChild(alertDiv);
        })
}
Edit Index.cshtml, update the GetLogTypeButtonId click event to use fetchLogTypeById:
var id = parseInt(radioButton.value);
//getLogTypeById(id);
fetchLogTypeById(id);

Build, run and test. If all looks good, verify function with IE 11.

IE Fetch Is Undefined

You can detect if the browser supports fetch.

if ('fetch' in window) {
    fetchLogTypeById(id);
}
else {
    getLogTypeById(id);
}

Or you can add small shims to add fetch and promise features.

Edit _Layout.cshtml, add the shims before Bootstrap js:
<script src="https://cdn.jsdelivr.net/npm/es6-promise@4.2.6/dist/es6-promise.min.js"
        integrity="sha256-wrMwufXWH6CASTr4Tgj/NzNAG57wFeknGdKbWhe7sXs="
        crossorigin="anonymous">
</script>
<script src="https://cdn.jsdelivr.net/npm/es6-promise@4.2.6/dist/es6-promise.auto.min.js"
        integrity="sha256-8qFPvAMQLj9hOXkNoEO0iOXQx2tHyA8XWkym5O3dxqM="
        crossorigin="anonymous">
</script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.4/fetch.min.js"
        integrity="sha256-eOUokb/RjDw7kS+vDwbatNrLN8BIvvEhlLM5yogcDIo="
        crossorigin="anonymous">
</script>

If you have configured libman.json, (see Manage Client-Side Libraries) you can add the libraries and use local links.

Edit libman.json, add the libraries:
{
    "provider": "unpkg",
    "library": "es6-promise@4.2.6",
    "files": [
        "dist/es6-promise.js",
        "dist/es6-promise.auto.js",
        "dist/es6-promise.auto.map",
        "dist/es6-promise.auto.min.js",
        "dist/es6-promise.auto.min.map",
        "dist/es6-promise.map",
        "dist/es6-promise.min.js",
        "dist/es6-promise.min.map"
    ],
    "destination": "wwwroot/lib/es6-promise/"
},
{
    "library": "fetch@2.0.4",
    "destination": "wwwroot/lib/fetch"
}
Edit _Layout.cshtml, add the shims before Bootstrap js:
<script src="~/lib/es6-promise/dist/es6-promise.min.js"></script>
<script src="~/lib/es6-promise/dist/es6-promise.auto.min.js"></script>
<script src="~/lib/fetch/fetch.min.js"></script>

Comment Count = 0

Please log in to comment.

Login Register
Logged in users receive web notifications.
Web Notifications