ASP.NET Core MVC Toast Notification System
This adds a toast notification system that displays toasts at the center bottom of the screen. It works efficiently for both synchronous and asynchronous toast display.
// ToastMessage.cs
public class ToastMessage
{
public string Message { get; set; } = string.Empty;
public ToastType Type { get; set; }
public int Duration { get; set; } = 5000;
}
// ToastType.cs
public enum ToastType
{
Success,
Error,
Warning,
Info
}
// ToastService.cs
public class ToastService(
ITempDataDictionaryFactory tempDataFactory,
IHttpContextAccessor httpContextAccessor) : IToastService
{
private const string _toastKey = "Toast";
public void AddToast(string message, ToastType type = ToastType.Info, int duration = 5000)
{
if (string.IsNullOrEmpty(message)) return;
var tempData = tempDataFactory.GetTempData(httpContextAccessor.HttpContext);
var toast = new ToastMessage
{
Message = message,
Type = type,
Duration = duration
};
tempData[_toastKey] = JsonSerializer.Serialize(toast);
}
public void AddSuccessToast(string message, int duration = 5000) =>
AddToast(message, ToastType.Success, duration);
public void AddErrorToast(string message, int duration = 5000) =>
AddToast(message, ToastType.Error, duration);
public void AddWarningToast(string message, int duration = 5000) =>
AddToast(message, ToastType.Warning, duration);
public void AddInfoToast(string message, int duration = 5000) =>
AddToast(message, ToastType.Info, duration);
}
// IToastService.cs
public interface IToastService
{
void AddErrorToast(string message, int duration = 5000);
void AddInfoToast(string message, int duration = 5000);
void AddSuccessToast(string message, int duration = 5000);
void AddToast(string message, ToastType type = ToastType.Info, int duration = 5000);
void AddWarningToast(string message, int duration = 5000);
}
Create a _Toast.cshtml partial
@using System.Text.Json
@using YourNamespace.Models
@inject ITempDataDictionaryFactory TempDataFactory
@inject IHttpContextAccessor HttpContextAccessor
@* Toast container *@
<div aria-live="polite" aria-atomic="true" class="position-fixed bottom-0 start-50 translate-middle-x p-3" style="z-index: 1050;">
<div id="toastContainer" class="toast-container d-flex flex-column align-items-center"></div>
</div>
@* Toast styles *@
<style>
.custom-toast {
min-width: 250px;
max-width: 400px;
padding: 12px 16px;
margin-bottom: 10px;
border-radius: 6px;
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
justify-content: flex-start;
gap: 8px;
opacity: 0;
transition: opacity 0.4s ease-in-out, transform 0.4s ease-in-out;
transform: translateY(100%);
}
.custom-toast.show {
opacity: 1;
transform: translateY(0);
}
</style>
<script>
const toastConfig = {
icons: {
'Success': 'bi-check-circle-fill',
'Error': 'bi-exclamation-circle-fill',
'Warning': 'bi-exclamation-triangle-fill',
'Info': 'bi-info-circle-fill'
},
bgColors: {
'Success': 'bg-success',
'Error': 'bg-danger',
'Warning': 'bg-warning',
'Info': 'bg-primary'
},
textColors: {
'Success': 'text-white',
'Error': 'text-white',
'Warning': 'text-dark',
'Info': 'text-white'
}
};
@* Process any server-side toasts stored in TempData *@
document.addEventListener("DOMContentLoaded", function () {
@{
var tempData = TempDataFactory.GetTempData(HttpContextAccessor.HttpContext);
if (tempData.TryGetValue("Toast", out var toastJson) && toastJson != null)
{
var toast = JsonSerializer.Deserialize<ToastMessage>((string)toastJson);
if (toast != null && !string.IsNullOrEmpty(toast.Message))
{
<text>
showToast('@Html.Raw(toast.Message.Replace("'", "\\'"))', '@toast.Type', @toast.Duration);
</text>
}
}
}
});
</script>
<script>
const ToastService = {
// Configuration for toast appearance
config: {
icons: {
'Success': 'bi-check-circle-fill',
'Error': 'bi-exclamation-circle-fill',
'Warning': 'bi-exclamation-triangle-fill',
'Info': 'bi-info-circle-fill'
},
bgColors: {
'Success': 'bg-success',
'Error': 'bg-danger',
'Warning': 'bg-warning',
'Info': 'bg-primary'
},
textColors: {
'Success': 'text-white',
'Error': 'text-white',
'Warning': 'text-dark',
'Info': 'text-white'
}
},
// Show a toast notification
show: function(message, type = 'Info', duration = 5000) {
const toastContainer = document.getElementById("toastContainer");
if (!toastContainer) {
console.error("Toast container not found");
return;
}
const toastElement = document.createElement("div");
// Set classes based on type
const bgColor = this.config.bgColors[type] || this.config.bgColors.Info;
const textColor = this.config.textColors[type] || this.config.textColors.Info;
const icon = this.config.icons[type] || this.config.icons.Info;
toastElement.classList.add("custom-toast", bgColor, textColor);
// Create toast
toastElement.innerHTML = `
<span><i class="bi ${icon}"></i></span>
<span>${message}</span>
`;
toastContainer.appendChild(toastElement);
// Show animation
setTimeout(() => {
toastElement.classList.add("show");
}, 100);
// Auto dismiss after duration
if (duration > 0) {
setTimeout(() => {
if (toastElement && toastElement.parentNode === toastContainer) {
toastElement.classList.remove("show");
setTimeout(() => {
if (toastElement && toastElement.parentNode === toastContainer) {
toastElement.remove();
}
}, 400);
}
}, duration);
}
},
// Helper methods for different toast types
showSuccess: function(message, duration = 5000) {
this.show(message, 'Success', duration);
},
showError: function(message, duration = 5000) {
this.show(message, 'Error', duration);
},
showWarning: function(message, duration = 5000) {
this.show(message, 'Warning', duration);
},
showInfo: function(message, duration = 5000) {
this.show(message, 'Info', duration);
},
};
// Expose a global function to show toasts
window.showToast = function(message, type = 'Info', duration = 5000) {
ToastService.show(message, type, duration);
};
// Helper functions for different toast types
window.showSuccessToast = function(message, duration = 5000) {
ToastService.showSuccess(message, duration);
};
window.showErrorToast = function(message, duration = 5000) {
ToastService.showError(message, duration);
};
window.showWarningToast = function(message, duration = 5000) {
ToastService.showWarning(message, duration);
};
window.showInfoToast = function(message, duration = 5000) {
ToastService.showInfo(message, duration);
};
</script>
Finally, add the partial to a file, such as the _Layout.cshtml file
<partial name="_Toast" />