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.
This commit is contained in:
@@ -4,7 +4,9 @@
|
|||||||
@using WebApp.Models
|
@using WebApp.Models
|
||||||
@using WebApp.Services
|
@using WebApp.Services
|
||||||
@using Heron.MudCalendar
|
@using Heron.MudCalendar
|
||||||
|
@using Microsoft.Extensions.Logging
|
||||||
@inject IEventOccurrenceService EventOccurrenceService
|
@inject IEventOccurrenceService EventOccurrenceService
|
||||||
|
@inject ILogger<Index> Logger
|
||||||
|
|
||||||
<PageHeader Title="Event Calendar" Description="View competition schedules and event occurrences" Icon="@AppIcons.EventCalendar">
|
<PageHeader Title="Event Calendar" Description="View competition schedules and event occurrences" Icon="@AppIcons.EventCalendar">
|
||||||
<ActionButtons>
|
<ActionButtons>
|
||||||
@@ -23,7 +25,7 @@
|
|||||||
<MudCalendar T="CalendarEventItem"
|
<MudCalendar T="CalendarEventItem"
|
||||||
Items="_calendarItems"
|
Items="_calendarItems"
|
||||||
View="CalendarView.Day"
|
View="CalendarView.Day"
|
||||||
CurrentDay=_calendarDate
|
CurrentDay="@_calendarDate"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
@@ -39,29 +41,115 @@
|
|||||||
|
|
||||||
private async Task LoadCalendarEvents()
|
private async Task LoadCalendarEvents()
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Loading calendar events");
|
||||||
var occurrences = await EventOccurrenceService.GetEventOccurrencesAsync();
|
var occurrences = await EventOccurrenceService.GetEventOccurrencesAsync();
|
||||||
_calendarItems = occurrences
|
|
||||||
.Select(occ => new CalendarEventItem(occ))
|
if (occurrences == null)
|
||||||
.ToList();
|
{
|
||||||
|
Logger.LogWarning("Service returned null occurrences");
|
||||||
|
_calendarItems = new List<CalendarEventItem>();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogDebug("Received {Count} occurrences from service", occurrences.Count());
|
||||||
|
|
||||||
|
var items = new List<CalendarEventItem>();
|
||||||
|
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
|
// Find the next date with events
|
||||||
_calendarDate = GetNextDateWithEvents();
|
_calendarDate = GetNextDateWithEvents();
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Error loading calendar events");
|
||||||
|
_calendarItems = new List<CalendarEventItem>();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private DateTime GetNextDateWithEvents()
|
private DateTime GetNextDateWithEvents()
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (_calendarItems == null || !_calendarItems.Any())
|
if (_calendarItems == null || !_calendarItems.Any())
|
||||||
{
|
{
|
||||||
|
Logger.LogDebug("No calendar items available, returning today's date");
|
||||||
return DateTime.Today;
|
return DateTime.Today;
|
||||||
}
|
}
|
||||||
|
|
||||||
var today = DateTime.Today;
|
var today = DateTime.Today;
|
||||||
var nextEvent = _calendarItems
|
var nextEvent = _calendarItems
|
||||||
.Where(item => item.Start.Date >= today)
|
.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)
|
.OrderBy(item => item.Start)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
return nextEvent?.Start.Date ?? _calendarItems.OrderBy(item => item.Start).First().Start.Date;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ public class CalendarEventItem : CalendarItem
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public CalendarEventItem()
|
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)
|
public CalendarEventItem(Core.Entities.EventOccurrence occurrence, Core.Entities.EventDefinition? eventDefinition = null)
|
||||||
|
|||||||
+26
-1
@@ -1,5 +1,6 @@
|
|||||||
using Data;
|
using Data;
|
||||||
using Microsoft.AspNetCore.DataProtection;
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MudBlazor.Services;
|
using MudBlazor.Services;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
@@ -268,7 +269,31 @@ app.UseRouting();
|
|||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
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.UseAntiforgery();
|
||||||
|
|
||||||
app.MapRazorComponents<App>()
|
app.MapRazorComponents<App>()
|
||||||
|
|||||||
Reference in New Issue
Block a user