Fehlerbehandlung, Logging und feldbezogene Validierung (Kapitel 13.2)

Zentrale Exception-Behandlung (Dispatcher.UIThread.UnhandledException,
AppDomain, TaskScheduler) verhindert Abstürze und protokolliert Fehler
über AppLogger in eine rotierende Log-Datei im App-Datenverzeichnis.
Toast-Benachrichtigungen zeigen Erfolg/Fehler global an. Alle Dialoge
mit Formularfeldern zeigen Validierungsmeldungen jetzt direkt am
betroffenen Feld statt in einem Sammel-Label.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 12:38:30 +02:00
co-authored by Claude Sonnet 5
parent 273f459119
commit db1afff7e4
27 changed files with 578 additions and 102 deletions
@@ -0,0 +1,55 @@
using Avalonia.Threading;
using LehrerApp.Core.Services;
namespace LehrerApp.Desktop.Services;
/// <summary>
/// Zentrale Exception-Behandlung (13.2.1): protokolliert jeden unerwarteten Fehler und zeigt
/// eine verständliche Meldung statt eines rohen Absturzes.
///
/// Reichweite ehrlich betrachtet: <see cref="Dispatcher"/>.UIThread.UnhandledException kann
/// Ausnahmen aus Befehlen/Ereignis-Handlern auf dem UI-Thread abfangen und die App am Leben
/// erhalten (Handled = true) — das deckt den weit überwiegenden Teil realer Abstürze in einer
/// Desktop-App ab. AppDomain.UnhandledException und TaskScheduler.UnobservedTaskException sind
/// Sicherheitsnetze für Fehler außerhalb des UI-Threads; die App kann eine "IsTerminating"-
/// Ausnahme dort nicht mehr verhindern, aber wenigstens vollständig protokollieren, bevor sie endet.
/// </summary>
public static class GlobalExceptionHandler
{
private static AppLogger? _logger;
private static NotificationService? _notifications;
public static void Install(AppLogger logger, NotificationService? notifications = null)
{
_logger = logger;
_notifications = notifications;
Dispatcher.UIThread.UnhandledException += OnDispatcherUnhandledException;
AppDomain.CurrentDomain.UnhandledException += OnAppDomainUnhandledException;
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
}
/// Muss aufgerufen werden, sobald der DI-Container steht, damit spätere Fehler
/// (die vor dem Container auftreten könnten) noch als Toast sichtbar werden.
public static void AttachNotifications(NotificationService notifications) => _notifications = notifications;
private static void OnDispatcherUnhandledException(object? sender, DispatcherUnhandledExceptionEventArgs e)
{
_logger?.Error("Unbehandelter Fehler auf dem UI-Thread.", e.Exception);
_notifications?.ShowError("Es ist ein unerwarteter Fehler aufgetreten. Details wurden protokolliert.");
e.Handled = true; // App am Leben halten statt abzustürzen.
}
private static void OnAppDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
_logger?.Error(
$"Unbehandelter Fehler außerhalb des UI-Threads (IsTerminating={e.IsTerminating}).",
e.ExceptionObject as Exception);
}
private static void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
{
_logger?.Error("Unbeobachtete Ausnahme in einem Hintergrund-Task.", e.Exception);
e.SetObserved(); // verhindert, dass die Ausnahme unbemerkt verschwindet.
}
}