From 71c6fbcbbe560f9c2f65f47d3417c154209c05f3 Mon Sep 17 00:00:00 2001 From: Ken Tucker Date: Wed, 1 Jan 2025 12:11:12 -0500 Subject: [PATCH 1/3] Enhance exception handling and logging across the app Added a private field `_exception` and a constructor to `ResultCompletionEventArgs` to handle exceptions. Wrapped initialization and activation logic in `Screen.cs` with try-catch blocks to log errors and handle exceptions. Introduced new virtual methods `OnActivatedAsyncException`, `OnDeactivateAsyncException`, and `OnInitializedAsyncException` in `Screen.cs` to handle exceptions during asynchronous operations. Added a new method `CoroutineException` in `CaliburnApplication.cs`, `FormsApplication.cs`, `CaliburnApplicationDelegate.cs`, and `Bootstrapper.cs` to handle exceptions during coroutine execution. Added event handlers for `Coroutine.Completed` in these files to invoke `CoroutineException` when an error occurs. Made minor formatting changes and added missing namespaces in various files to ensure consistency and proper functionality. Removed unused `using` directives and added necessary ones in `BootstrapperBase.cs` and other files. Added logging for exceptions in `BootstrapperBase.cs` and ensured that coroutine exceptions are logged. --- .../ResultCompletionEventArgs.cs | 6 ++ src/Caliburn.Micro.Core/Screen.cs | 68 ++++++++++++++++--- .../Platforms/Android/CaliburnApplication.cs | 13 +++- .../Maui/Android/CaliburnApplication.cs | 24 +++++-- .../Platforms/Maui/FormsApplication.cs | 26 +++++-- .../Maui/Windows/CaliburnApplication.cs | 7 ++ .../Maui/iOS/CaliburnApplicationDelegate.cs | 19 ++++-- .../iOS/CaliburnApplicationDelegate.cs | 13 +++- .../Platforms/net46-netcore/Bootstrapper.cs | 22 ++++-- .../Platforms/net46/Bootstrapper.cs | 21 ++++-- .../netcore-avalonia/BootstrapperBase.cs | 28 ++++++++ .../Platforms/uap/CaliburnApplication.cs | 18 ++++- 12 files changed, 226 insertions(+), 39 deletions(-) diff --git a/src/Caliburn.Micro.Core/ResultCompletionEventArgs.cs b/src/Caliburn.Micro.Core/ResultCompletionEventArgs.cs index e6e86b19..33615673 100644 --- a/src/Caliburn.Micro.Core/ResultCompletionEventArgs.cs +++ b/src/Caliburn.Micro.Core/ResultCompletionEventArgs.cs @@ -18,5 +18,11 @@ public class ResultCompletionEventArgs : EventArgs /// /// true if cancelled; otherwise, false. public bool WasCancelled; + private Exception _exception; + + public ResultCompletionEventArgs(Exception exception) + { + _exception = exception; + } } } diff --git a/src/Caliburn.Micro.Core/Screen.cs b/src/Caliburn.Micro.Core/Screen.cs index 80c68f65..5a6dcdd5 100644 --- a/src/Caliburn.Micro.Core/Screen.cs +++ b/src/Caliburn.Micro.Core/Screen.cs @@ -103,20 +103,33 @@ async Task IActivate.ActivateAsync(CancellationToken cancellationToken) if (!IsInitialized) { + try + { + Log.Info("Initializing {0}.", this); #pragma warning disable CS0618 // Type or member is obsolete - await OnInitializeAsync(cancellationToken); + await OnInitializeAsync(cancellationToken); #pragma warning restore CS0618 // Type or member is obsolete - IsInitialized = initialized = true; - await OnInitializedAsync(cancellationToken); + IsInitialized = initialized = true; + await OnInitializedAsync(cancellationToken); + } + catch (Exception ex) + { + Log.Error(ex); + } } - - Log.Info("Activating {0}.", this); + try + { + Log.Info("Activating {0}.", this); #pragma warning disable CS0618 // Type or member is obsolete - await OnActivateAsync(cancellationToken); + await OnActivateAsync(cancellationToken); #pragma warning restore CS0618 // Type or member is obsolete - IsActive = true; - await OnActivatedAsync(cancellationToken); - + IsActive = true; + await OnActivatedAsync(cancellationToken); + } + catch (Exception ex) + { + OnActivatedAsyncException(ex); + } await (Activated?.InvokeAllAsync(this, new ActivationEventArgs { WasInitialized = initialized @@ -134,7 +147,15 @@ async Task IDeactivate.DeactivateAsync(bool close, CancellationToken cancellatio }); Log.Info("Deactivating {0}.", this); - await OnDeactivateAsync(close, cancellationToken); + try + { + await OnDeactivateAsync(close, cancellationToken); + } + catch (Exception ex) + { + OnDeactivateAsyncException(ex); + } + IsActive = false; await (Deactivated?.InvokeAllAsync(this, new DeactivationEventArgs @@ -206,6 +227,15 @@ protected virtual Task OnActivateAsync(CancellationToken cancellationToken) } + /// + /// Called when exception called in OnActivatedAsync. + /// + protected virtual void OnActivatedAsyncException(Exception thrownException) + { + Log.Info("Activated Async Exception"); + Log.Error(thrownException); + } + /// /// Called when view has been activated. /// @@ -226,5 +256,23 @@ protected virtual Task OnDeactivateAsync(bool close, CancellationToken cancellat Log.Info("Task deactivate"); return Task.FromResult(true); } + + /// + /// Called when exception called in OnDectivateAsync. + /// + protected virtual void OnDeactivateAsyncException(Exception thrownException) + { + Log.Info("Deactivate Async Exception"); + Log.Error(thrownException); + } + + /// + /// Called when exception called in OnInitializedAsync. + /// + protected virtual void OnInitializedAsyncException(Exception thrownException) + { + Log.Info("Initialized Async Exception"); + Log.Error(thrownException); + } } } diff --git a/src/Caliburn.Micro.Platform/Platforms/Android/CaliburnApplication.cs b/src/Caliburn.Micro.Platform/Platforms/Android/CaliburnApplication.cs index 371de01a..61b78f65 100644 --- a/src/Caliburn.Micro.Platform/Platforms/Android/CaliburnApplication.cs +++ b/src/Caliburn.Micro.Platform/Platforms/Android/CaliburnApplication.cs @@ -21,7 +21,7 @@ public class CaliburnApplication : Application public CaliburnApplication(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) { - + } /// @@ -41,7 +41,7 @@ protected virtual void StartDesignTime() /// /// Called by the bootstrapper's constructor at runtime to start the framework. - /// B + /// protected virtual void StartRuntime() { AssemblySourceCache.Install(); @@ -131,5 +131,14 @@ protected virtual IEnumerable GetAllInstances(Type service) protected virtual void BuildUp(object instance) { } + + /// + /// Handles exceptions that occur during coroutine execution. + /// + /// The sender of the event. + /// The event arguments containing exception details. + protected virtual void CoroutineException(object sender, ResultCompletionEventArgs e) + { + } } } diff --git a/src/Caliburn.Micro.Platform/Platforms/Maui/Android/CaliburnApplication.cs b/src/Caliburn.Micro.Platform/Platforms/Maui/Android/CaliburnApplication.cs index 074c54cc..130cbb9c 100644 --- a/src/Caliburn.Micro.Platform/Platforms/Maui/Android/CaliburnApplication.cs +++ b/src/Caliburn.Micro.Platform/Platforms/Maui/Android/CaliburnApplication.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; using System.Reflection; -using Android.App; using Android.Runtime; -using Microsoft.Maui; -using Microsoft.Maui.Hosting; namespace Caliburn.Micro.Maui { @@ -23,7 +20,7 @@ public abstract class CaliburnApplication : Microsoft.Maui.MauiApplication public CaliburnApplication(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) { - + } /// @@ -39,7 +36,7 @@ protected virtual void StartDesignTime() /// /// Called by the bootstrapper's constructor at runtime to start the framework. - /// B + /// protected virtual void StartRuntime() { AssemblySourceCache.Install(); @@ -79,6 +76,14 @@ protected void Initialize() { StartRuntime(); } + + Coroutine.Completed += (s, e) => + { + if (e.Error != null) + { + CoroutineException(s, e); + } + }; } /// @@ -97,6 +102,13 @@ protected virtual IEnumerable SelectAssemblies() return new[] { GetType().GetTypeInfo().Assembly }; } - + /// + /// Handles exceptions that occur during coroutine execution. + /// + /// The sender of the event. + /// The event arguments containing the exception details. + protected virtual void CoroutineException(object sender, ResultCompletionEventArgs e) + { + } } } diff --git a/src/Caliburn.Micro.Platform/Platforms/Maui/FormsApplication.cs b/src/Caliburn.Micro.Platform/Platforms/Maui/FormsApplication.cs index 5a86e585..6d9a6f8f 100644 --- a/src/Caliburn.Micro.Platform/Platforms/Maui/FormsApplication.cs +++ b/src/Caliburn.Micro.Platform/Platforms/Maui/FormsApplication.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.Maui.Controls; @@ -19,8 +18,10 @@ public abstract class MauiApplication : Application /// /// Start the framework. /// - protected void Initialize() { - if (isInitialized) { + protected void Initialize() + { + if (isInitialized) + { return; } @@ -30,10 +31,11 @@ protected void Initialize() { var baseExtractTypes = AssemblySourceCache.ExtractTypes; - AssemblySourceCache.ExtractTypes = assembly => { + AssemblySourceCache.ExtractTypes = assembly => + { var baseTypes = baseExtractTypes(assembly); var elementTypes = assembly.ExportedTypes - .Where(t => typeof (Element).GetTypeInfo().IsAssignableFrom(t.GetTypeInfo())); + .Where(t => typeof(Element).GetTypeInfo().IsAssignableFrom(t.GetTypeInfo())); return baseTypes.Union(elementTypes); }; @@ -44,6 +46,13 @@ protected void Initialize() { IoC.BuildUp = BuildUp; AssemblySource.Instance.Refresh(); + Coroutine.Completed += (s, e) => + { + if (e.Error != null) + { + CoroutineException(s, e); + } + }; } /// @@ -174,5 +183,12 @@ protected virtual IEnumerable GetAllInstances(Type service) protected virtual void BuildUp(object instance) { } + + /// + /// Called by the bootstrapper's constructor at design time to start the framework. + /// + protected virtual void CoroutineException(object sender, ResultCompletionEventArgs e) + { + } } } diff --git a/src/Caliburn.Micro.Platform/Platforms/Maui/Windows/CaliburnApplication.cs b/src/Caliburn.Micro.Platform/Platforms/Maui/Windows/CaliburnApplication.cs index cad83b59..2c60869c 100644 --- a/src/Caliburn.Micro.Platform/Platforms/Maui/Windows/CaliburnApplication.cs +++ b/src/Caliburn.Micro.Platform/Platforms/Maui/Windows/CaliburnApplication.cs @@ -141,5 +141,12 @@ protected virtual void OnUnhandledException(object sender, Microsoft.UI.Xaml.Unh { } + /// + /// Called by the bootstrapper's constructor at design time to start the framework. + /// + protected virtual void CoroutineException(object sender, ResultCompletionEventArgs e) + { + } + } } diff --git a/src/Caliburn.Micro.Platform/Platforms/Maui/iOS/CaliburnApplicationDelegate.cs b/src/Caliburn.Micro.Platform/Platforms/Maui/iOS/CaliburnApplicationDelegate.cs index 66588d0b..51699f6a 100644 --- a/src/Caliburn.Micro.Platform/Platforms/Maui/iOS/CaliburnApplicationDelegate.cs +++ b/src/Caliburn.Micro.Platform/Platforms/Maui/iOS/CaliburnApplicationDelegate.cs @@ -1,8 +1,5 @@ -using System; using System.Collections.Generic; using System.Reflection; -using Foundation; -using UIKit; using Microsoft.Maui; namespace Caliburn.Micro.Maui @@ -86,6 +83,13 @@ protected void Initialize() } else StartRuntime(); + Coroutine.Completed += (s, e) => + { + if (e.Error != null) + { + CoroutineException(s, e); + } + }; } /// @@ -101,7 +105,14 @@ protected virtual void Configure() /// A list of assemblies to inspect. protected virtual IEnumerable SelectAssemblies() { - return new[] {GetType().GetTypeInfo().Assembly}; + return new[] { GetType().GetTypeInfo().Assembly }; + } + + /// + /// Called by the bootstrapper's constructor at design time to start the framework. + /// + protected virtual void CoroutineException(object sender, ResultCompletionEventArgs e) + { } } diff --git a/src/Caliburn.Micro.Platform/Platforms/iOS/CaliburnApplicationDelegate.cs b/src/Caliburn.Micro.Platform/Platforms/iOS/CaliburnApplicationDelegate.cs index 64a5ad6c..74b3844c 100644 --- a/src/Caliburn.Micro.Platform/Platforms/iOS/CaliburnApplicationDelegate.cs +++ b/src/Caliburn.Micro.Platform/Platforms/iOS/CaliburnApplicationDelegate.cs @@ -106,7 +106,7 @@ protected virtual void Configure() /// A list of assemblies to inspect. protected virtual IEnumerable SelectAssemblies() { - return new[] {GetType().GetTypeInfo().Assembly}; + return new[] { GetType().GetTypeInfo().Assembly }; } /// @@ -127,7 +127,7 @@ protected virtual object GetInstance(Type service, string key) /// The located services. protected virtual IEnumerable GetAllInstances(Type service) { - return new[] {Activator.CreateInstance(service)}; + return new[] { Activator.CreateInstance(service) }; } /// @@ -137,5 +137,14 @@ protected virtual IEnumerable GetAllInstances(Type service) protected virtual void BuildUp(object instance) { } + + /// + /// Handles exceptions that occur during coroutine execution. + /// + /// The sender of the event. + /// The event arguments containing exception details. + protected virtual void CoroutineException(object sender, ResultCompletionEventArgs e) + { + } } } diff --git a/src/Caliburn.Micro.Platform/Platforms/net46-netcore/Bootstrapper.cs b/src/Caliburn.Micro.Platform/Platforms/net46-netcore/Bootstrapper.cs index 93b31e1c..724a5c10 100644 --- a/src/Caliburn.Micro.Platform/Platforms/net46-netcore/Bootstrapper.cs +++ b/src/Caliburn.Micro.Platform/Platforms/net46-netcore/Bootstrapper.cs @@ -44,7 +44,6 @@ public void Initialize() PlatformProvider.Current = new XamlPlatformProvider(); - var baseExtractTypes = AssemblySourceCache.ExtractTypes; AssemblySourceCache.ExtractTypes = assembly => @@ -75,6 +74,13 @@ public void Initialize() { StartRuntime(); } + Coroutine.Completed += (s, e) => + { + if (e.Error != null) + { + CoroutineException(s, e); + } + }; } /// @@ -136,7 +142,7 @@ protected virtual void Configure() /// A list of assemblies to inspect. protected virtual IEnumerable SelectAssemblies() { - return new[] {GetType().Assembly}; + return new[] { GetType().Assembly }; } /// @@ -160,7 +166,7 @@ protected virtual object GetInstance(Type service, string key) /// The located services. protected virtual IEnumerable GetAllInstances(Type service) { - return new[] {Activator.CreateInstance(service)}; + return new[] { Activator.CreateInstance(service) }; } /// @@ -209,7 +215,6 @@ protected async Task DisplayRootViewForAsync(Type viewModelType, IDictionary /// Locates the view model, locates the associate view, binds them and shows it as the root view. /// @@ -219,5 +224,14 @@ protected Task DisplayRootViewForAsync(IDictionary s { return DisplayRootViewForAsync(typeof(TViewModel), settings); } + + /// + /// Override this to handle coroutine exceptions. + /// + /// The sender. + /// The event args. + protected virtual void CoroutineException(object sender, ResultCompletionEventArgs e) + { + } } } diff --git a/src/Caliburn.Micro.Platform/Platforms/net46/Bootstrapper.cs b/src/Caliburn.Micro.Platform/Platforms/net46/Bootstrapper.cs index 93b31e1c..ea72ca4a 100644 --- a/src/Caliburn.Micro.Platform/Platforms/net46/Bootstrapper.cs +++ b/src/Caliburn.Micro.Platform/Platforms/net46/Bootstrapper.cs @@ -3,8 +3,6 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; -using System.Windows; -using System.Windows.Threading; namespace Caliburn.Micro { @@ -75,6 +73,14 @@ public void Initialize() { StartRuntime(); } + + Coroutine.Completed += (s, e) => + { + if (e.Error != null) + { + CoroutineException(s, e); + } + }; } /// @@ -136,7 +142,7 @@ protected virtual void Configure() /// A list of assemblies to inspect. protected virtual IEnumerable SelectAssemblies() { - return new[] {GetType().Assembly}; + return new[] { GetType().Assembly }; } /// @@ -160,7 +166,7 @@ protected virtual object GetInstance(Type service, string key) /// The located services. protected virtual IEnumerable GetAllInstances(Type service) { - return new[] {Activator.CreateInstance(service)}; + return new[] { Activator.CreateInstance(service) }; } /// @@ -219,5 +225,12 @@ protected Task DisplayRootViewForAsync(IDictionary s { return DisplayRootViewForAsync(typeof(TViewModel), settings); } + + /// + /// Called by the bootstrapper's constructor at design time to start the framework. + /// + protected virtual void CoroutineException(object sender, ResultCompletionEventArgs e) + { + } } } diff --git a/src/Caliburn.Micro.Platform/Platforms/netcore-avalonia/BootstrapperBase.cs b/src/Caliburn.Micro.Platform/Platforms/netcore-avalonia/BootstrapperBase.cs index 3f7ffae5..7cff0b9b 100644 --- a/src/Caliburn.Micro.Platform/Platforms/netcore-avalonia/BootstrapperBase.cs +++ b/src/Caliburn.Micro.Platform/Platforms/netcore-avalonia/BootstrapperBase.cs @@ -6,6 +6,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Threading; namespace Caliburn.Micro { @@ -66,6 +67,13 @@ public void Initialize() { StartRuntime(); } + Coroutine.Completed += (s, e) => + { + if (e.Error != null) + { + CoroutineException(s, e); + } + }; } /// @@ -119,6 +127,16 @@ protected virtual void Configure() { } + /// + /// Override this to catch unhandled exceptions. + /// + /// The sender. + /// The event args. + protected virtual void OnUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) + { + Log.Error(e.Exception); + } + /// /// Override to tell the framework where to find assemblies to inspect for views, etc. /// @@ -202,5 +220,15 @@ protected async Task DisplayRootViewFor(IDictionary { await DisplayRootViewForAsync(typeof(TViewModel), settings); } + + /// + /// Handles coroutine exceptions. + /// + /// The sender. + /// The event args. + protected virtual void CoroutineException(object sender, ResultCompletionEventArgs e) + { + Log.Error(e.Error ?? new Exception("Coroutine Error")); + } } } diff --git a/src/Caliburn.Micro.Platform/Platforms/uap/CaliburnApplication.cs b/src/Caliburn.Micro.Platform/Platforms/uap/CaliburnApplication.cs index 34d0432b..cf2b43d6 100644 --- a/src/Caliburn.Micro.Platform/Platforms/uap/CaliburnApplication.cs +++ b/src/Caliburn.Micro.Platform/Platforms/uap/CaliburnApplication.cs @@ -97,6 +97,13 @@ protected void Initialize() { StartRuntime(); } + Coroutine.Completed += (s, e) => + { + if (e.Error != null) + { + CoroutineException(s, e); + } + }; } /// @@ -137,7 +144,7 @@ protected virtual void Configure() /// A list of assemblies to inspect. protected virtual IEnumerable SelectAssemblies() { - return new[] {GetType().GetTypeInfo().Assembly}; + return new[] { GetType().GetTypeInfo().Assembly }; } /// @@ -158,7 +165,7 @@ protected virtual object GetInstance(Type service, string key) /// The located services. protected virtual IEnumerable GetAllInstances(Type service) { - return new[] {System.Activator.CreateInstance(service)}; + return new[] { System.Activator.CreateInstance(service) }; } /// @@ -301,5 +308,12 @@ protected Task DisplayRootViewForAsync(CancellationToken cancellationToken) /// The view model type. /// A task that represents the asynchronous operation. protected Task DisplayRootViewForAsync() => DisplayRootViewForAsync(CancellationToken.None); + + /// + /// Called by the bootstrapper's constructor at design time to start the framework. + /// + protected virtual void CoroutineException(object sender, ResultCompletionEventArgs e) + { + } } } From a96c66935091581e7418f4b7fbc647dfa39e2d6e Mon Sep 17 00:00:00 2001 From: Ken Tucker Date: Wed, 1 Jan 2025 13:34:05 -0500 Subject: [PATCH 2/3] fix the build --- src/Caliburn.Micro.Core/ResultCompletionEventArgs.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Caliburn.Micro.Core/ResultCompletionEventArgs.cs b/src/Caliburn.Micro.Core/ResultCompletionEventArgs.cs index 33615673..457ed3d7 100644 --- a/src/Caliburn.Micro.Core/ResultCompletionEventArgs.cs +++ b/src/Caliburn.Micro.Core/ResultCompletionEventArgs.cs @@ -18,11 +18,12 @@ public class ResultCompletionEventArgs : EventArgs /// /// true if cancelled; otherwise, false. public bool WasCancelled; - private Exception _exception; - public ResultCompletionEventArgs(Exception exception) + /// + /// Initializes a new instance of the class. + /// + public ResultCompletionEventArgs() { - _exception = exception; } } } From d1e6c0f8ceb57dcf643c7480a99333c619551257 Mon Sep 17 00:00:00 2001 From: Ken Tucker Date: Sat, 4 Jan 2025 21:58:14 -0500 Subject: [PATCH 3/3] Fix some of the errors --- src/Caliburn.Micro.Core/Screen.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Caliburn.Micro.Core/Screen.cs b/src/Caliburn.Micro.Core/Screen.cs index 5a6dcdd5..5b6ba43f 100644 --- a/src/Caliburn.Micro.Core/Screen.cs +++ b/src/Caliburn.Micro.Core/Screen.cs @@ -115,6 +115,7 @@ async Task IActivate.ActivateAsync(CancellationToken cancellationToken) catch (Exception ex) { Log.Error(ex); + OnInitializedAsyncException(ex); } } try @@ -128,6 +129,7 @@ async Task IActivate.ActivateAsync(CancellationToken cancellationToken) } catch (Exception ex) { + Log.Error(ex); OnActivatedAsyncException(ex); } await (Activated?.InvokeAllAsync(this, new ActivationEventArgs @@ -153,6 +155,7 @@ async Task IDeactivate.DeactivateAsync(bool close, CancellationToken cancellatio } catch (Exception ex) { + Log.Error(ex); OnDeactivateAsyncException(ex); }