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.

ebeeraheem

View Profile
68 views
Jul 11, 2025
// 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" />