1. Fixed Misleading Property Names

- File: WebApp/ChapterSettings.cs
  - Change: Renamed StateContainer.UserId to ScheduledTeams
  - Impact: Property name now accurately reflects what it stores

  2.  Added Structured Logging with Serilog

  - Packages Added:
    - Serilog.AspNetCore
    - Serilog.Sinks.Console
    - Serilog.Sinks.File
  - Files Modified:
    - Program.cs - Added Serilog configuration with console and file logging
    - appsettings.json - Added Serilog minimum log levels
    - appsettings.Development.json - Added Debug level logging for development
  - Benefits:
    - Structured log output for better parsing/analysis
    - Automatic file rotation (daily, 30 days retention)
    - Logs stored in logs/webapp-.txt
    - Better formatted console output

  3.  Added Global Error Handling

  - File Created: WebApp/Components/Shared/AppErrorBoundary.razor
  - File Modified: WebApp/Components/App.razor
  - Features:
    - Catches unhandled exceptions throughout the app
    - Shows detailed error info in Development environment
    - Shows user-friendly message in Production
    - Logs errors automatically
    - Provides "Return to Home" button

  4.  Enhanced Input Validation

  - File Modified: WebApp/Components/Login.razor
  - Validations Added:
    - Email: Required, valid email format, max 100 chars, regex validation
    - Password: Required, min 8 chars, max 100 chars
  - Benefits:
    - Client-side validation before submission
    - Clear error messages for users
    - Prevents invalid data submission
This commit is contained in:
2025-12-03 14:10:08 -05:00
parent 54875e970c
commit a0313687da
10 changed files with 1217 additions and 2 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ namespace WebApp
{
private int[]? _scheduledTeams;
public int[] UserId
public int[] ScheduledTeams
{
get => _scheduledTeams ?? [];
set
+2
View File
@@ -13,7 +13,9 @@
</head>
<body>
<AppErrorBoundary>
<Routes @rendermode="InteractiveServer" />
</AppErrorBoundary>
<script src="_framework/blazor.web.js"></script>
<script src="@Assets["_content/MudBlazor/MudBlazor.min.js"]"></script>
+10
View File
@@ -98,8 +98,18 @@
private class LoginModel
{
[Required(ErrorMessage = "Email is required")]
[EmailAddress(ErrorMessage = "Invalid email format")]
[MaxLength(100, ErrorMessage = "Email must be less than 100 characters")]
[RegularExpression(@"^[^@\s]+@[^@\s]+\.[^@\s]+$", ErrorMessage = "Please enter a valid email address")]
public string Email { get; set; } = string.Empty;
[Required(ErrorMessage = "Password is required")]
[MinLength(8, ErrorMessage = "Password must be at least 8 characters")]
[MaxLength(100, ErrorMessage = "Password must be less than 100 characters")]
[DataType(DataType.Password)]
public string Password { get; set; } = string.Empty;
public bool RememberMe { get; set; }
}
@@ -0,0 +1,55 @@
@using Microsoft.AspNetCore.Components.Web
<ErrorBoundary>
<ChildContent>
@ChildContent
</ChildContent>
<ErrorContent Context="ex">
<MudContainer MaxWidth="MaxWidth.Medium" Class="mt-8">
<MudPaper Elevation="3" Class="pa-6">
<MudAlert Severity="Severity.Error" Variant="Variant.Filled">
<MudText Typo="Typo.h5" Class="mb-2">
<MudIcon Icon="@Icons.Material.Filled.Error" Class="mr-2" />
An Error Occurred
</MudText>
@if (ShowDetails)
{
<MudText Typo="Typo.body2" Class="mt-4">
<strong>Error:</strong> @ex.Message
</MudText>
<MudText Typo="Typo.caption" Class="mt-2">
<strong>Stack Trace:</strong>
<pre style="overflow-x: auto;">@ex.StackTrace</pre>
</MudText>
}
else
{
<MudText Typo="Typo.body2">
Something went wrong. Please try refreshing the page or contact support if the problem persists.
</MudText>
}
</MudAlert>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
Class="mt-4"
OnClick="@(() => Navigation.NavigateTo("/", forceLoad: true))">
Return to Home
</MudButton>
</MudPaper>
</MudContainer>
</ErrorContent>
</ErrorBoundary>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
[Inject] private IWebHostEnvironment Environment { get; set; } = default!;
[Inject] private NavigationManager Navigation { get; set; } = default!;
[Inject] private ILogger<AppErrorBoundary> Logger { get; set; } = default!;
private bool ShowDetails => Environment.IsDevelopment();
protected override void OnParametersSet()
{
Logger.LogError("Error boundary triggered");
}
}
File diff suppressed because it is too large Load Diff
+14
View File
@@ -1,12 +1,26 @@
using Data;
using Microsoft.EntityFrameworkCore;
using MudBlazor.Services;
using Serilog;
using WebApp;
using WebApp.Authentication;
using WebApp.Components;
var builder = WebApplication.CreateBuilder(args);
// Configure Serilog
builder.Host.UseSerilog((context, configuration) =>
configuration
.ReadFrom.Configuration(context.Configuration)
.Enrich.FromLogContext()
.WriteTo.Console(
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}")
.WriteTo.File(
path: "logs/webapp-.txt",
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 30,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}"));
// Configure authentication secrets for production (Docker, etc.)
if (builder.Environment.IsProduction())
{
+3
View File
@@ -28,6 +28,9 @@
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.22.1" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
<PackageReference Include="MudBlazor" Version="8.14.0" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
+10
View File
@@ -4,5 +4,15 @@
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Serilog": {
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Warning",
"Microsoft.AspNetCore": "Warning",
"System": "Warning"
}
}
}
}
+10
View File
@@ -5,6 +5,16 @@
"Microsoft.AspNetCore": "Warning"
}
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.AspNetCore": "Warning",
"System": "Warning"
}
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"SQLiteDefault": "Data Source=data.db"
+7
View File
@@ -0,0 +1,7 @@
2025-12-03 14:07:50.779 -05:00 [INF] Login rate limiting service started {"SourceContext":"WebApp.Authentication.LoginRateLimitService"}
2025-12-03 14:07:53.133 -05:00 [ERR] Error boundary triggered {"SourceContext":"WebApp.Components.Shared.AppErrorBoundary","RequestId":"0HNHIJT0PE9UJ:00000001","RequestPath":"/login","ConnectionId":"0HNHIJT0PE9UJ"}
2025-12-03 14:08:08.603 -05:00 [DBG] Successful credential validation for admin@test.com {"SourceContext":"WebApp.Authentication.AuthenticationService","ActionId":"fab60e78-16b9-40cd-897c-fed8413ce202","ActionName":"WebApp.Authentication.AuthController.CookieLogin (tsa-chapter-organizer)","RequestId":"0HNHIJT0PE9UK:00000001","RequestPath":"/Auth/CookieLogin","ConnectionId":"0HNHIJT0PE9UK"}
2025-12-03 14:08:08.626 -05:00 [INF] Successful login for admin@test.com (Administrator) from ::1 {"SourceContext":"WebApp.Authentication.AuthController","ActionId":"fab60e78-16b9-40cd-897c-fed8413ce202","ActionName":"WebApp.Authentication.AuthController.CookieLogin (tsa-chapter-organizer)","RequestId":"0HNHIJT0PE9UK:00000001","RequestPath":"/Auth/CookieLogin","ConnectionId":"0HNHIJT0PE9UK"}
2025-12-03 14:08:08.666 -05:00 [ERR] Error boundary triggered {"SourceContext":"WebApp.Components.Shared.AppErrorBoundary","RequestId":"0HNHIJT0PE9UK:00000003","RequestPath":"/","ConnectionId":"0HNHIJT0PE9UK"}
2025-12-03 14:08:13.026 -05:00 [WRN] Compiling a query which loads related collections for more than one collection navigation, either via 'Include' or through projection, but no 'QuerySplittingBehavior' has been configured. By default, Entity Framework will use 'QuerySplittingBehavior.SingleQuery', which can potentially result in slow query performance. See https://go.microsoft.com/fwlink/?linkid=2134277 for more information. To identify the query that's triggering this warning call 'ConfigureWarnings(w => w.Throw(RelationalEventId.MultipleCollectionIncludeWarning))'. {"EventId":{"Id":20504,"Name":"Microsoft.EntityFrameworkCore.Query.MultipleCollectionIncludeWarning"},"SourceContext":"Microsoft.EntityFrameworkCore.Query","TransportConnectionId":"u30JsHVho23GyKv2giOOPQ","RequestId":"0HNHIJT0PE9UK:00000011","RequestPath":"/_blazor","ConnectionId":"0HNHIJT0PE9UK"}
2025-12-03 14:08:14.140 -05:00 [WRN] Compiling a query which loads related collections for more than one collection navigation, either via 'Include' or through projection, but no 'QuerySplittingBehavior' has been configured. By default, Entity Framework will use 'QuerySplittingBehavior.SingleQuery', which can potentially result in slow query performance. See https://go.microsoft.com/fwlink/?linkid=2134277 for more information. To identify the query that's triggering this warning call 'ConfigureWarnings(w => w.Throw(RelationalEventId.MultipleCollectionIncludeWarning))'. {"EventId":{"Id":20504,"Name":"Microsoft.EntityFrameworkCore.Query.MultipleCollectionIncludeWarning"},"SourceContext":"Microsoft.EntityFrameworkCore.Query","TransportConnectionId":"u30JsHVho23GyKv2giOOPQ","RequestId":"0HNHIJT0PE9UK:00000011","RequestPath":"/_blazor","ConnectionId":"0HNHIJT0PE9UK"}