Files
LehrerApp/LehrerApp.Desktop/Services/GlobalExceptionHandler.cs
2026-08-13 19:57:48 +02:00

64 lines
2.8 KiB
C#

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.
///
/// <see cref="Dispatcher"/>.UIThread.UnhandledException fängt Fehler aus Befehlen und
/// Ereignis-Handlern ab. Nur erwartbar wiederherstellbare I/O-, Netzwerk-, Timeout- und
/// Abbruchfehler werden behandelt; unbekannte Zustandsfehler dürfen die App beenden.
/// 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);
e.Handled = IsRecoverable(e.Exception);
if (e.Handled)
_notifications?.ShowError("Der Vorgang ist fehlgeschlagen. Details wurden protokolliert.");
}
private static bool IsRecoverable(Exception exception) => exception is
IOException or
UnauthorizedAccessException or
HttpRequestException or
TimeoutException or
OperationCanceledException;
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.
}
}