From c2812ffce285d7eae612a9d333283a36fb9c1cc0 Mon Sep 17 00:00:00 2001 From: Neil Brommer Date: Mon, 10 Jul 2023 10:16:19 -0700 Subject: [PATCH] Add a post on alerts in ASP.NET Core --- src/posts/AspNetCoreAlerts.md | 197 ++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 src/posts/AspNetCoreAlerts.md diff --git a/src/posts/AspNetCoreAlerts.md b/src/posts/AspNetCoreAlerts.md new file mode 100644 index 0000000..b140bc1 --- /dev/null +++ b/src/posts/AspNetCoreAlerts.md @@ -0,0 +1,197 @@ +--- +title: ASP.NET Core Alerts +description: Tools to display alerts across redirects +tags: [ Programming, C#, ASP.NET Core ] +--- + +This code allows you to store alerts that will be displayed the next time the user is served a page. This is useful for when you want to display an alert after a redirect. + +## How To Use + +To add an alert use one of the extension methods to add an alert to the session: + +```csharp +return this.RedirectToPage("/Things/Index") + .WithSuccess("Did the thing!"); +``` + +Then to display the alerts, add something like this to the `_Layout.cshtml`: + +```csharp +@{ + Alert[] alerts = this.TempData.GetAlerts(); +} + +@if (alerts != null && alerts.Any()) +{ + foreach (Alert alert in alerts) + { + ... + } +} +``` + +Note that calling `TempData.GetAlerts()` will also remove all current alerts from the session so they won't be displayed on following page loads. + +## Code + +The alerts class: + +```csharp +public class Alert +{ + public AlertType AlertType { get; set; } + public string Text { get; set; } + + public Alert(AlertType alertType, string text) + { + this.AlertType = alertType; + this.Text = text; + } +} + +public enum AlertType +{ + Success, + Info, + Warning, + Error +} +``` + +The `AlertActionResult` To wrap the return `ActionResult` and actually add the alert to the session: + +```csharp +/// +/// This is a wrapper around another action result. When executed, this adds the given alert to +/// TempData and executes the inner ActionResult +/// +public class AlertActionResult : ActionResult +{ + public ActionResult InnerResult { get; set; } + public Alert Alert { get; set; } + + public AlertActionResult(ActionResult innerResult, Alert alert) + { + this.InnerResult = innerResult; + this.Alert = alert; + } + + public AlertActionResult(ActionResult innerResult, AlertType alertType, string alertText) + { + this.InnerResult = innerResult; + this.Alert = new Alert(alertType, alertText); + } + + public override void ExecuteResult(ActionContext context) + { + ITempDataDictionary tempData = context.HttpContext.RequestServices + .GetService() + .GetTempData(context.HttpContext); + + tempData.AddAlert(this.Alert); + + this.InnerResult.ExecuteResult(context); + } + + public override async Task ExecuteResultAsync(ActionContext context) + { + ITempDataDictionary tempData = context.HttpContext.RequestServices + .GetService() + .GetTempData(context.HttpContext); + + tempData.AddAlert(this.Alert); + + if (this.InnerResult is PageResult pageResult) + { + // Need to do some additional setup for Razor Pages + // See https://stackoverflow.com/questions/55989209/ + + PageContext pageContext = context as PageContext + ?? throw new ArgumentException("The context must be a PageContext for Razor Pages", + nameof(context)); + + Func pageFactory = pageContext.HttpContext.RequestServices + .GetRequiredService() + .CreatePageFactory(pageContext.ActionDescriptor); + + ViewContext viewContext = new( + pageContext, + NullView.Instance, + pageContext.ViewData, + tempData, + TextWriter.Null, + new HtmlHelperOptions()) + { + ExecutingFilePath = pageContext.ActionDescriptor.RelativePath + }; + + pageResult.ViewData = viewContext.ViewData; + pageResult.Page = (PageBase)pageFactory(pageContext, viewContext); + } + + await this.InnerResult.ExecuteResultAsync(context); + } + + private class NullView : IView + { + public static readonly NullView Instance = new(); + + public string Path => string.Empty; + + public Task RenderAsync(ViewContext context) + { + ArgumentNullException.ThrowIfNull(context, nameof(context)); + + return Task.CompletedTask; + } + } +} +``` + +And some extension methods for adding and getting alerts: + +```csharp +public static class AspNetExtensions +{ + private static readonly string _alertsTempDataKey = "atticAlerts"; + + public static Alert[] GetAlerts(this ITempDataDictionary tempData) + { + if (!tempData.ContainsKey(_alertsTempDataKey)) + tempData[_alertsTempDataKey] = JsonSerializer.Serialize(Array.Empty()); + + return JsonSerializer.Deserialize(tempData[_alertsTempDataKey] as string); + } + + public static void AddAlert(this ITempDataDictionary tempData, Alert alert) + { + Alert[] currentAlerts = tempData.GetAlerts(); + + currentAlerts = currentAlerts + .Concat(new Alert[] { alert }) + .ToArray(); + tempData[_alertsTempDataKey] = JsonSerializer.Serialize(currentAlerts); + } + + public static ActionResult WithSuccess(this ActionResult actionResult, string alertText) + { + return new AlertActionResult(actionResult, AlertType.Success, alertText); + } + + public static ActionResult WithInfo(this ActionResult actionResult, string alertText) + { + return new AlertActionResult(actionResult, AlertType.Info, alertText); + } + + public static ActionResult WithWarning(this ActionResult actionResult, string alertText) + { + return new AlertActionResult(actionResult, AlertType.Warning, alertText); + } + + public static ActionResult WithError(this ActionResult actionResult, string alertText) + { + return new AlertActionResult(actionResult, AlertType.Error, alertText); + } +} +```