ASP.NET Core 8.0 - Message Generator
This article will describe the implementation of a global Bootstrap message modal. You should review the earlier articles of the Cookies And Claims Project series. Registered users can download the ASP.NET Core 8.0 - Cookies And Claims Project for free.
Cookies And Claims Project and Article Series
Free project download for registered users!
I developed the Cookies And Claims Project (CACP) to demonstrate a simple cookie authentication scheme and claim-based authorization with a clear and modifiable design. The CACP is developed with Visual Studio 2022 and the MS Long Term Support (LTS) version .NET 8.0 framework. All Errors, Warnings, and Messages from Code Analysis have been mitigated. The CACP implements utilities like an AES Cipher, Password Hasher, native JavaScript client form validation, password requirements UI, Bootstrap Native message modal generator, loading/spinner generator, SignalR online user count, and an automatic idle logout for users with administration permissions.
- ASP.NET Core 8.0 - Cookies And Claims
- ASP.NET Core 8.0 - Cookie Authentication
- ASP.NET Core 8.0 - Remember Me Or Not
- ASP.NET Core 8.0 - Authorized Access
- ASP.NET Core 8.0 - Administrator Claim
- ASP.NET Core 8.0 - Admin Idle Logout
- ASP.NET Core 8.0 - Cookie Consent
- ASP.NET Core 8.0 - SignalR Online User Count
- ASP.NET Core 8.0 - AES Cipher
- ASP.NET Core 8.0 - Password Hasher
- ASP.NET Core 8.0 - Message Generator
The CACP implements a group of utilities: AES Cipher, Password Hasher, Password Validation, Message Generator, and Spinner Generator. This article describes the Message Generator. I developed a Bootstrap message modal for ASP.NET Core v3.1 over 3 years ago. See ASP.NET Core 3.1 - Message Modal.
A message to inform the user something went wrong, and they need to start over is the most important. This oops message
needs to prevent the user from continuing on the current page and provide a link to the start over page. The first oops
message in the Users Without Passwords Project was a Bootstrap-jQuery modal with no dismiss button, a link to start over,
a 'static' backdrop and keyboard=false. I liked it enough to develop a success message to present before automatically
navigating to the goal. I liked that enough to develop message-modal.js which dynamically creates the html. Variables
reference the created modal components which allow the message modal to co-exist with other modals. It can be loaded
in _Layout. cshtml
for global access or used locally by loading from the page. The
showMessageModal()
function has defaults and parameters. Since then I developed an AJAX Modal and a
Confirm Modal. I bundled all 3 into a dynamic-modals.js library. This library supports Bootstrap v4 and v5 css and works with
component libraries like jQuery, Bootstrap.js, and Bootstrap Native.
dynamic-modals.js
'use strict' /*! * Copyright © Ken Haggerty (https://kenhaggerty.com) * Licensed under the MIT License. * dynamic-modals.js - Version 2.0.0 * Bootstrap v5 and v4 Message Modal - Version 2.2.3 * Bootstrap v5 and v4 AJAX Modal - Version 2.2.4 * Bootstrap v5 and v4 Confirm Modal - Version 1.2.2 * Depends on Bootstrap v5 js, Bootstrap Native, or jQuery with Bootstrap js */ let modalLibrary = ''; let modalInstance = null; /*Bootstrap v5 and v4 Message Modal - Version 2.2.3 */ let messageModalDefaults = { Message: 'Thank you for supporting KenHaggerty.Com.', MessageClass: 'alert-primary', MessageAllowDismiss: true, MessageDismissText: 'OK', MessageDismissClass: 'btn-primary', MessageIncludeLink: false, MessageLinkHref: 'https://kenhaggerty.com', MessageLinkTarget: '_self', MessageLinkText: 'KenHaggerty.Com', MessageLinkClass: 'btn-primary', MessageHideBackdrop: false } let modalBodyDiv = document.createElement('div'); modalBodyDiv.classList.add('modal-body'); modalBodyDiv.classList.add('text-center'); let messageElement = document.createElement('h4'); messageElement.classList.add('text-break'); let messageClassDiv = document.createElement('div'); messageClassDiv.appendChild(messageElement); modalBodyDiv.appendChild(messageClassDiv); let messageLinkButton = document.createElement('a', { href: '/' }); modalBodyDiv.appendChild(messageLinkButton); let messageDismissButton = document.createElement('button', { type: 'button' }); messageDismissButton.dataset.dismiss = 'modal'; // v4 messageDismissButton.dataset.bsDismiss = 'modal'; // v5 modalBodyDiv.appendChild(messageDismissButton); let modalContentDiv = document.createElement('div'); modalContentDiv.classList.add('modal-content'); modalContentDiv.appendChild(modalBodyDiv); let modalDialogDiv = document.createElement('div', { role: 'document' }); modalDialogDiv.classList.add('modal-dialog'); modalDialogDiv.classList.add('top-5'); modalDialogDiv.appendChild(modalContentDiv); let modalDiv = document.createElement('div', { tabindex: '-1', role: 'dialog', ariaHidden: "true" }); modalDiv.classList.add('modal'); modalDiv.classList.add('message-modal'); modalDiv.classList.add('fade'); modalDiv.appendChild(modalDialogDiv); document.body.appendChild(modalDiv); function showMessageModal( message = messageModalDefaults.Message, messageClass = messageModalDefaults.MessageClass, allowDismiss = messageModalDefaults.MessageAllowDismiss, dismissText = messageModalDefaults.MessageDismissText, dismissClass = messageModalDefaults.MessageDismissClass, includeLink = messageModalDefaults.MessageIncludeLink, linkHref = messageModalDefaults.MessageLinkHref, linkTarget = messageModalDefaults.MessageLinkTarget, linkText = messageModalDefaults.MessageLinkText, linkClass = messageModalDefaults.MessageLinkClass, hideBackdrop = messageModalDefaults.MessageHideBackdrop) { if (modalLibrary.length == 0) return; if (message.length === 0) message = messageModalDefaults.Message; let messageParse = message.toString(); if (messageParse.indexOf('DOMException') !== -1) message = messageParse.substring(14); if (messageParse.indexOf('NotAllowedError') !== -1) message = messageParse.substring(17); if (messageClass.length === 0) messageClass = messageModalDefaults.MessageClass; if (dismissText.length === 0) dismissText = messageModalDefaults.MessageDismissText; if (dismissClass.length === 0) dismissClass = messageModalDefaults.MessageDismissClass; if (linkHref.length === 0) linkHref = messageModalDefaults.MessageLinkHref; if (linkTarget.length === 0) linkTarget = messageModalDefaults.MessageLinkTarget; if (linkText.length === 0) linkText = messageModalDefaults.MessageLinkText; if (linkClass.length === 0) linkClass = messageModalDefaults.MessageLinkClass; messageElement.innerText = message; messageClassDiv.classList.remove(...messageClassDiv.classList); messageClassDiv.classList.add('alert', messageClass, 'px-2', 'py-1', 'mb-3'); messageDismissButton.textContent = dismissText; messageDismissButton.classList.remove(...messageDismissButton.classList); messageDismissButton.classList.add('btn'); messageDismissButton.classList.add(dismissClass); messageLinkButton.classList.add('d-none'); if (includeLink) { messageLinkButton.setAttribute('href', linkHref); messageLinkButton.setAttribute('target', linkTarget); messageLinkButton.textContent = linkText; messageLinkButton.classList.remove(...messageLinkButton.classList); messageLinkButton.classList.add('btn'); messageLinkButton.classList.add(linkClass); if (allowDismiss) { messageLinkButton.classList.add('mr-2', 'me-2'); // v4 & v5 } else { messageDismissButton.classList.add('d-none'); if (modalLibrary == 'BV5') { modalInstance = new bootstrap.Modal(modalDiv, { backdrop: 'static', keyboard: false }); modalInstance.show(); } else if (modalLibrary == 'BSN') { modalInstance = new BSN.Modal(modalDiv, { backdrop: 'static', keyboard: false }); modalInstance.show(); } else if (modalLibrary == 'JQ$') { $('.message-modal').modal('dispose'); $('.message-modal').modal({ backdrop: 'static', keyboard: false }); $('.message-modal').modal('show'); } return; } } if (hideBackdrop) { if (modalLibrary == 'BV5') { modalInstance = new bootstrap.Modal(modalDiv, { backdrop: false, keyboard: false }); modalInstance.show(); } else if (modalLibrary == 'BSN') { modalInstance = new BSN.Modal(modalDiv, { backdrop: false, keyboard: false }); modalInstance.show(); } else if (modalLibrary == 'JQ$') { $('.message-modal').modal('dispose'); $('.message-modal').modal({ backdrop: false, keyboard: false }); $('.message-modal').modal('show'); } } else { if (modalLibrary == 'BV5') { modalInstance = new bootstrap.Modal(modalDiv); modalInstance.show(); } else if (modalLibrary == 'BSN') { modalInstance = new BSN.Modal(modalDiv); modalInstance.show(); } else if (modalLibrary == 'JQ$') { $('.message-modal').modal('dispose'); $('.message-modal').modal('show'); } } } /*Bootstrap v5 and v4 AJAX Modal - Version 2.2.4 */ let defaultLoadingAjaxTitle = 'Details'; let defaultUpdateAjaxTitle = 'Edit'; let modalAjaxCloseButton = document.createElement('button'); modalAjaxCloseButton.type = 'button'; modalAjaxCloseButton.ariaHidden = 'Close'; modalAjaxCloseButton.classList.add('btn-close'); modalAjaxCloseButton.dataset.dismiss = 'modal'; // v4 modalAjaxCloseButton.dataset.bsDismiss = 'modal'; // v5 let modalAjaxTitle = document.createElement('h5'); modalAjaxTitle.classList.add('modal-title'); modalAjaxTitle.textContent = defaultLoadingAjaxTitle; let modalHeader = document.createElement('div'); modalHeader.classList.add('modal-header'); modalHeader.appendChild(modalAjaxTitle); modalHeader.appendChild(modalAjaxCloseButton); let modalAjaxBody = document.createElement('div'); modalAjaxBody.classList.add('modal-body'); let modalAjaxUpdateButton = document.createElement('button'); modalAjaxUpdateButton.type = 'button'; modalAjaxUpdateButton.classList.add('btn', 'btn-success', 'ajax-btn-update', 'd-none'); modalAjaxUpdateButton.innerText = 'Update'; let modalAjaxFooterCloseButton = document.createElement('button'); modalAjaxFooterCloseButton.type = 'button'; modalAjaxFooterCloseButton.classList.add('btn', 'btn-primary'); modalAjaxFooterCloseButton.dataset.bsDismiss = 'modal'; modalAjaxFooterCloseButton.innerText = 'Close'; let modalAjaxFooter = document.createElement('div'); modalAjaxFooter.classList.add('modal-footer'); modalAjaxFooter.appendChild(modalAjaxUpdateButton); modalAjaxFooter.appendChild(modalAjaxFooterCloseButton); let modalAjaxContentDiv = document.createElement('div'); modalAjaxContentDiv.classList.add('modal-content'); modalAjaxContentDiv.appendChild(modalHeader); modalAjaxContentDiv.appendChild(modalAjaxBody); modalAjaxContentDiv.appendChild(modalAjaxFooter); let modalAjaxDialogDiv = document.createElement('div', { role: 'document' }); modalAjaxDialogDiv.classList.add('modal-dialog'); modalAjaxDialogDiv.classList.add('top-5'); modalAjaxDialogDiv.appendChild(modalAjaxContentDiv); let modalAjaxDiv = document.createElement('div', { tabindex: '-1', role: 'dialog', ariaHidden: 'true' }); modalAjaxDiv.classList.add('modal', 'ajax-modal', 'fade'); modalAjaxDiv.appendChild(modalAjaxDialogDiv); document.body.appendChild(modalAjaxDiv); let spinnerAjaxDiv = document.createElement('div'); spinnerAjaxDiv.classList.add('spinner-border', 'text-alert', 'd-flex', 'mt-2', 'mx-auto'); spinnerAjaxDiv.setAttribute('role', 'status'); let modalAjaxSpan = document.createElement('span'); modalAjaxSpan.classList.add('visually-hidden'); modalAjaxSpan.innerText = 'Loading...'; spinnerAjaxDiv.appendChild(modalAjaxSpan); let modalAjaxUpdateHiddenInput = document.createElement('input'); modalAjaxUpdateHiddenInput.setAttribute('type', 'hidden'); modalAjaxUpdateHiddenInput.setAttribute('id', 'AjaxModalHiddenId'); let modalAjaxUpdateHeader = document.createElement('h6'); let modalAjaxUpdateInput = document.createElement('textarea'); modalAjaxUpdateInput.setAttribute('id', 'AjaxModalUpdateInputId'); modalAjaxUpdateInput.classList.add('form-control'); function showLoadingModal(title) { if (modalLibrary.length == 0) return; modalAjaxUpdateButton.classList.add('d-none'); modalAjaxFooterCloseButton.innerText = 'Close'; modalAjaxFooterCloseButton.classList.remove('btn-secondary'); modalAjaxFooterCloseButton.classList.add('btn-primary'); if (title !== '') modalAjaxTitle.textContent = title; else modalAjaxTitle.textContent = defaultLoadingAjaxTitle; modalAjaxBody.innerHTML = ''; modalAjaxBody.appendChild(spinnerAjaxDiv); if (modalLibrary == 'BV5') { modalInstance = new bootstrap.Modal(modalAjaxDiv); modalInstance.show(); } else if (modalLibrary == 'BSN') { modalInstance = new BSN.Modal(modalAjaxDiv); modalInstance.show(); } else if (modalLibrary == 'JQ$') { $('.ajax-modal').modal('dispose'); $('.ajax-modal').modal('show'); } } function showUpdateModal(id, value, header = '', title = '', rows = 1) { modalAjaxUpdateButton.classList.remove('d-none'); modalAjaxFooterCloseButton.innerText = 'Cancel'; modalAjaxFooterCloseButton.classList.remove('btn-primary'); modalAjaxFooterCloseButton.classList.add('btn-secondary'); if (title !== '') modalAjaxTitle.textContent = title; else modalAjaxTitle.textContent = defaultUpdateAjaxTitle; modalAjaxBody.innerHTML = ''; modalAjaxUpdateHiddenInput.value = id; modalAjaxBody.appendChild(modalAjaxUpdateHiddenInput); modalAjaxUpdateHeader.textContent = header === '' ? value : header; modalAjaxBody.appendChild(modalAjaxUpdateHeader); modalAjaxUpdateInput.rows = rows; modalAjaxUpdateInput.value = value; modalAjaxBody.appendChild(modalAjaxUpdateInput); if (modalLibrary == 'BV5') { modalInstance = new bootstrap.Modal(modalAjaxDiv); modalInstance.show(); } else if (modalLibrary == 'BSN') { modalInstance = new BSN.Modal(modalAjaxDiv); modalInstance.show(); } else if (modalLibrary == 'JQ$') { $('.ajax-modal').modal('dispose'); $('.ajax-modal').modal('show'); } } function showResponseModal(responseJSON) { modalAjaxUpdateButton.classList.add('d-none'); modalAjaxFooterCloseButton.innerText = 'Close'; modalAjaxFooterCloseButton.classList.remove('btn-secondary'); modalAjaxFooterCloseButton.classList.add('btn-primary'); let dl, dt, dd, prop; modalAjaxBody.innerHTML = ''; let status = ''; if (responseJSON.Status !== undefined) status = responseJSON.Status; if (responseJSON.status !== undefined) status = responseJSON.status; if (status === 'Success') { dl = document.createElement('dl'); for (prop in responseJSON) { if (responseJSON[prop] == null) continue; dt = document.createElement('dt'); dt.textContent = prop; dl.appendChild(dt); dd = document.createElement('dd'); dd.classList.add('text-break'); dd.textContent = responseJSON[prop].length === 0 ? 'Not Found' : responseJSON[prop]; dl.appendChild(dd); } let successDiv = document.createElement('div'); successDiv.classList.add('alert', 'alert-success'); successDiv.appendChild(dl); modalAjaxBody.appendChild(successDiv); } else { dl = document.createElement('dl'); for (prop in responseJSON) { dt = document.createElement('dt'); dt.textContent = prop; dl.appendChild(dt); dd = document.createElement('dd'); dd.classList.add('text-break'); dd.textContent = responseJSON[prop].length === 0 ? 'Not Found' : responseJSON[prop]; dl.appendChild(dd); } let alertDiv = document.createElement('div'); alertDiv.classList.add('alert', 'alert-danger'); alertDiv.appendChild(dl); modalAjaxBody.appendChild(alertDiv); } } function showErrorModal(error) { modalAjaxUpdateButton.classList.add('d-none'); modalAjaxFooterCloseButton.innerText = 'Close'; modalAjaxFooterCloseButton.classList.remove('btn-secondary'); modalAjaxFooterCloseButton.classList.add('btn-primary'); modalAjaxTitle.textContent = 'An Error Has Occurred'; modalAjaxBody.innerHTML = ''; let alertDiv = document.createElement('div'); alertDiv.classList.add('alert', 'alert-danger'); alertDiv.textContent = error; modalAjaxBody.appendChild(alertDiv); if (modalLibrary == 'BV5') { modalInstance = new bootstrap.Modal(modalAjaxDiv); modalInstance.show(); } else if (modalLibrary == 'BSN') { modalInstance = new BSN.Modal(modalAjaxDiv); modalInstance.show(); } else if (modalLibrary == 'JQ$') { $('.ajax-modal').modal('dispose'); $('.ajax-modal').modal('show'); } } /*Bootstrap v5 and v4 Confirm Modal - Version 1.2.2 */ /* 1.2.2 Added alert class to confirmClassDiv. */ let confirmBodyDiv = document.createElement('div'); confirmBodyDiv.classList.add('modal-body', 'text-center'); let confirmElement = document.createElement('h4'); let confirmClassDiv = document.createElement('div'); confirmClassDiv.appendChild(confirmElement); confirmBodyDiv.appendChild(confirmClassDiv); let confirmButton = document.createElement('button', { type: 'button' }); confirmBodyDiv.appendChild(confirmButton); let cancelButton = document.createElement('button', { type: 'button' }); cancelButton.dataset.dismiss = 'modal'; // v4 cancelButton.dataset.bsDismiss = 'modal'; // v5 confirmBodyDiv.appendChild(cancelButton); let confirmContentDiv = document.createElement('div'); confirmContentDiv.classList.add('modal-content'); confirmContentDiv.appendChild(confirmBodyDiv); let confirmDialogDiv = document.createElement('div', { role: 'document' }); confirmDialogDiv.classList.add('modal-dialog', 'top-5'); confirmDialogDiv.appendChild(confirmContentDiv); let modalConfirmDiv = document.createElement('div', { tabindex: '-1', role: 'dialog', ariaHidden: "true" }); modalConfirmDiv.classList.add('modal', 'fade', 'confirm-modal'); modalConfirmDiv.appendChild(confirmDialogDiv); document.body.appendChild(modalConfirmDiv); let defaultConfirmMessage = 'Are you sure?'; let defaultConfirmedCallback = function () { alert('Callback is not set.') }; let defaultConfirmMessageClass = 'alert-primary'; let defaultConfirmButtonText = 'Delete'; let defaultConfirmButtonClass = 'btn-danger'; let defaultCancelButtonText = 'Cancel'; let defaultCancelButtonClass = 'btn-primary'; function showConfirmModal( confirmMessage = defaultConfirmMessage, confirmedCallback = defaultConfirmedCallback, confirmMessageClass = defaultConfirmMessageClass, confirmButtonText = defaultConfirmButtonText, confirmButtonClass = defaultConfirmButtonClass, cancelButtonText = defaultCancelButtonText, cancelButtonClass = defaultCancelButtonClass) { if (modalLibrary.length == 0) return; confirmElement.innerText = confirmMessage; confirmClassDiv.classList.remove(...confirmClassDiv.classList); confirmClassDiv.classList.add('alert', confirmMessageClass, 'px-2', 'py-1', 'mb-3'); confirmButton.textContent = confirmButtonText; confirmButton.classList.remove(...confirmButton.classList); confirmButton.classList.add('btn', 'mr-2', 'me-2', confirmButtonClass); // v4 & v5 confirmButton.addEventListener('click', confirmedCallback); cancelButton.textContent = cancelButtonText; cancelButton.classList.remove(...cancelButton.classList); cancelButton.classList.add('btn', cancelButtonClass); if (modalLibrary == 'BV5') { modalInstance = new bootstrap.Modal(modalConfirmDiv); modalInstance.show(); } else if (modalLibrary == 'BSN') { modalInstance = new BSN.Modal(modalConfirmDiv); modalInstance.show(); } else if (modalLibrary == 'JQ$') { $('.confirm-modal').modal('dispose'); $('.confirm-modal').modal('show'); } } document.addEventListener('DOMContentLoaded', function () { if (window.jQuery) { if ($.fn.modal) modalLibrary = 'JQ$'; else alert('Bootstrap-jQuery modal was not found.'); } else if (window.BSN) { if (window.BSN.Modal) modalLibrary = 'BSN'; else alert('BootstrapNative Modal was not found.'); } else if (window.bootstrap) { if (window.bootstrap.Modal) modalLibrary = 'BV5'; else alert('Bootstrap v5 Modal was not found.'); } else alert('Bootstrap v5 js, BootstrapNative, or jQuery is required by dynamic-modals.js.'); });
The CACP includes a Modal Message Generator which dynamically creates the minimal signature for the showMessageModal function with examples.
The CACP includes a sample of the AJAX Modal and Confirm Modal on the Admin page.
Comments(0)