From 2aaefb2491b6ac0da69e3a46293a0d90f25a5876 Mon Sep 17 00:00:00 2001 From: James Kolpack Date: Mon, 5 Jan 2026 13:21:03 -0500 Subject: [PATCH] Implement enhanced static file caching and improve calendar event loading with detailed logging This commit introduces a new static file caching strategy in Program.cs, optimizing cache headers for Blazor assets to improve performance and ensure fresh content after deployments. Additionally, the Calendar component in Index.razor has been updated to include comprehensive logging for event loading, handling null occurrences, and error management during calendar item creation. The CalendarEventItem model is also initialized to prevent null reference issues. These changes enhance the application's reliability and user experience. --- .../Components/Features/Calendar/Index.razor | 120 +++++++++++++++--- WebApp/Models/CalendarEventItem.cs | 4 + WebApp/Program.cs | 27 +++- 3 files changed, 134 insertions(+), 17 deletions(-) diff --git a/WebApp/Components/Features/Calendar/Index.razor b/WebApp/Components/Features/Calendar/Index.razor index fa9c98a..244776a 100644 --- a/WebApp/Components/Features/Calendar/Index.razor +++ b/WebApp/Components/Features/Calendar/Index.razor @@ -4,7 +4,9 @@ @using WebApp.Models @using WebApp.Services @using Heron.MudCalendar +@using Microsoft.Extensions.Logging @inject IEventOccurrenceService EventOccurrenceService +@inject ILogger Logger @@ -23,7 +25,7 @@ } @@ -39,29 +41,115 @@ private async Task LoadCalendarEvents() { - var occurrences = await EventOccurrenceService.GetEventOccurrencesAsync(); - _calendarItems = occurrences - .Select(occ => new CalendarEventItem(occ)) - .ToList(); + try + { + Logger.LogInformation("Loading calendar events"); + var occurrences = await EventOccurrenceService.GetEventOccurrencesAsync(); + + if (occurrences == null) + { + Logger.LogWarning("Service returned null occurrences"); + _calendarItems = new List(); + return; + } - // Find the next date with events - _calendarDate = GetNextDateWithEvents(); + Logger.LogDebug("Received {Count} occurrences from service", occurrences.Count()); + + var items = new List(); + foreach (var occ in occurrences) + { + try + { + if (occ == null) + { + Logger.LogWarning("Null occurrence found, skipping"); + continue; + } + + if (string.IsNullOrEmpty(occ.Name)) + { + Logger.LogWarning("Occurrence with Id={Id} has null or empty Name", occ.Id); + } + + var calendarItem = new CalendarEventItem(occ, occ.EventDefinition); + items.Add(calendarItem); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error creating CalendarEventItem for occurrence Id={Id}, Name={Name}", + occ?.Id, occ?.Name); + // Continue processing other items + } + } + + _calendarItems = items; + Logger.LogInformation("Created {Count} calendar items from {OccurrenceCount} occurrences", + _calendarItems.Count, occurrences.Count()); + + // Find the next date with events + _calendarDate = GetNextDateWithEvents(); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error loading calendar events"); + _calendarItems = new List(); + } + finally + { + StateHasChanged(); + } } private DateTime GetNextDateWithEvents() { - if (_calendarItems == null || !_calendarItems.Any()) + try { + if (_calendarItems == null || !_calendarItems.Any()) + { + Logger.LogDebug("No calendar items available, returning today's date"); + return DateTime.Today; + } + + var today = DateTime.Today; + var nextEvent = _calendarItems + .Where(item => + { + try + { + return item != null && item.Start.Date >= today; + } + catch (Exception ex) + { + Logger.LogWarning(ex, "Error checking item date, skipping item"); + return false; + } + }) + .OrderBy(item => item.Start) + .FirstOrDefault(); + + if (nextEvent != null) + { + return nextEvent.Start.Date; + } + + // Fallback to first event if no future events + var firstEvent = _calendarItems + .Where(item => item != null) + .OrderBy(item => item.Start) + .FirstOrDefault(); + + if (firstEvent != null) + { + return firstEvent.Start.Date; + } + + return DateTime.Today; + } + catch (Exception ex) + { + Logger.LogError(ex, "Error in GetNextDateWithEvents"); return DateTime.Today; } - - var today = DateTime.Today; - var nextEvent = _calendarItems - .Where(item => item.Start.Date >= today) - .OrderBy(item => item.Start) - .FirstOrDefault(); - - return nextEvent?.Start.Date ?? _calendarItems.OrderBy(item => item.Start).First().Start.Date; } } diff --git a/WebApp/Models/CalendarEventItem.cs b/WebApp/Models/CalendarEventItem.cs index f7a0aaf..b6cab82 100644 --- a/WebApp/Models/CalendarEventItem.cs +++ b/WebApp/Models/CalendarEventItem.cs @@ -24,6 +24,10 @@ public class CalendarEventItem : CalendarItem /// public CalendarEventItem() { + // Initialize base class properties to avoid null reference issues + Text = string.Empty; + Start = DateTime.MinValue; + End = DateTime.MinValue; } public CalendarEventItem(Core.Entities.EventOccurrence occurrence, Core.Entities.EventDefinition? eventDefinition = null) diff --git a/WebApp/Program.cs b/WebApp/Program.cs index 7820822..702e53e 100644 --- a/WebApp/Program.cs +++ b/WebApp/Program.cs @@ -1,5 +1,6 @@ using Data; using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.StaticFiles; using Microsoft.EntityFrameworkCore; using MudBlazor.Services; using Serilog; @@ -268,7 +269,31 @@ app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); -app.UseStaticFiles(); +// Configure static files with proper cache headers for Blazor assets +app.UseStaticFiles(new StaticFileOptions +{ + OnPrepareResponse = ctx => + { + // Blazor framework files: Use ETags with short cache and revalidation + // This allows caching for performance but ensures fresh files after deployments + // Browser will check ETag on each request (304 Not Modified if unchanged) + if (ctx.File.Name.Contains("_framework") || + ctx.File.Name.Contains("blazor") || + ctx.File.Name.EndsWith(".dll") || + ctx.File.Name.EndsWith(".wasm")) + { + // Cache for 12 hours, but must revalidate (check ETag) before using + // If file hasn't changed, browser gets 304 Not Modified (no download) + // If file changed, browser downloads new version + ctx.Context.Response.Headers.Append("Cache-Control", "public, max-age=43200, must-revalidate"); + } + else + { + // Other static files (CSS, images, etc.) can be cached long-term + ctx.Context.Response.Headers.Append("Cache-Control", "public, max-age=31536000"); + } + } +}); app.UseAntiforgery(); app.MapRazorComponents()