You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
143 lines
5.7 KiB
143 lines
5.7 KiB
using System; |
|
using System.Diagnostics.CodeAnalysis; |
|
using CommunityToolkit.Mvvm.Input; |
|
using NLog; |
|
using StabilityMatrix.Avalonia.Services; |
|
using StabilityMatrix.Core.Extensions; |
|
|
|
namespace StabilityMatrix.Avalonia.Extensions; |
|
|
|
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] |
|
public static class RelayCommandExtensions |
|
{ |
|
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); |
|
|
|
private static void VerifyFlowExceptionsToTaskSchedulerEnabled(IAsyncRelayCommand command) |
|
{ |
|
// Check that the FlowExceptionsToTaskScheduler flag is set |
|
var options = command.GetPrivateField<AsyncRelayCommandOptions>("options"); |
|
|
|
if (!options.HasFlag(AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler)) |
|
{ |
|
throw new ArgumentException( |
|
"The command must be created with the FlowExceptionsToTaskScheduler option enabled" |
|
); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Attach an error handler to the command that will invoke the given action when an exception occurs. |
|
/// </summary> |
|
/// <param name="command">The command to attach the error handler to.</param> |
|
/// <param name="onError">The action to invoke when an exception occurs.</param> |
|
/// <exception cref="ArgumentException">Thrown if the command was not created with the FlowExceptionsToTaskScheduler option enabled.</exception> |
|
public static T WithErrorHandler<T>(this T command, Action<Exception> onError) |
|
where T : IAsyncRelayCommand |
|
{ |
|
VerifyFlowExceptionsToTaskSchedulerEnabled(command); |
|
|
|
command.PropertyChanged += (sender, e) => |
|
{ |
|
if (sender is not IAsyncRelayCommand senderCommand) |
|
{ |
|
return; |
|
} |
|
// On ExecutionTask updates, check if there is an exception |
|
if ( |
|
e.PropertyName == nameof(AsyncRelayCommand.ExecutionTask) |
|
&& senderCommand.ExecutionTask is { Exception: { } exception } |
|
) |
|
{ |
|
onError(exception.InnerException ?? exception); |
|
} |
|
}; |
|
|
|
return command; |
|
} |
|
|
|
/// <summary> |
|
/// Conditionally attach an error handler to the command that will invoke the given action when an exception occurs. |
|
/// The error is propagated if not in DEBUG mode. |
|
/// </summary> |
|
/// <param name="command">The command to attach the error handler to.</param> |
|
/// <param name="onError">The action to invoke when an exception occurs.</param> |
|
/// <exception cref="ArgumentException">Thrown if the command was not created with the FlowExceptionsToTaskScheduler option enabled.</exception> |
|
public static T WithConditionalErrorHandler<T>(this T command, Action<Exception> onError) |
|
where T : IAsyncRelayCommand |
|
{ |
|
VerifyFlowExceptionsToTaskSchedulerEnabled(command); |
|
|
|
#if DEBUG |
|
command.PropertyChanged += (sender, e) => |
|
{ |
|
if (sender is not IAsyncRelayCommand senderCommand) |
|
{ |
|
return; |
|
} |
|
// On ExecutionTask updates, check if there is an exception |
|
if ( |
|
e.PropertyName == nameof(AsyncRelayCommand.ExecutionTask) |
|
&& senderCommand.ExecutionTask is { Exception: { } exception } |
|
) |
|
{ |
|
if (exception.InnerException != null) |
|
{ |
|
throw exception.InnerException; |
|
} |
|
throw exception; |
|
} |
|
}; |
|
|
|
return command; |
|
#else |
|
return WithErrorHandler(command, onError); |
|
#endif |
|
} |
|
|
|
/// <summary> |
|
/// Attach an error handler to the command that will log the error and show a notification. |
|
/// </summary> |
|
/// <param name="command">The command to attach the error handler to.</param> |
|
/// <param name="notificationService">The notification service to use to show the notification.</param> |
|
/// <param name="logLevel">The log level to use when logging the error. Defaults to LogLevel.Error</param> |
|
/// <exception cref="ArgumentException">Thrown if the command was not created with the FlowExceptionsToTaskScheduler option enabled.</exception> |
|
public static T WithNotificationErrorHandler<T>( |
|
this T command, |
|
INotificationService notificationService, |
|
LogLevel? logLevel = default |
|
) |
|
where T : IAsyncRelayCommand |
|
{ |
|
logLevel ??= LogLevel.Error; |
|
|
|
return command.WithErrorHandler(e => |
|
{ |
|
Logger.Log(logLevel, e, "Error executing command"); |
|
notificationService.ShowPersistent("Error", $"[{e.GetType().Name}] {e.Message}"); |
|
}); |
|
} |
|
|
|
/// <summary> |
|
/// Attach an error handler to the command that will log the error and show a notification. |
|
/// The error is propagated if not in DEBUG mode. |
|
/// </summary> |
|
/// <param name="command">The command to attach the error handler to.</param> |
|
/// <param name="notificationService">The notification service to use to show the notification.</param> |
|
/// <param name="logLevel">The log level to use when logging the error. Defaults to LogLevel.Error</param> |
|
/// <exception cref="ArgumentException">Thrown if the command was not created with the FlowExceptionsToTaskScheduler option enabled.</exception> |
|
public static T WithConditionalNotificationErrorHandler<T>( |
|
this T command, |
|
INotificationService notificationService, |
|
LogLevel? logLevel = default |
|
) |
|
where T : IAsyncRelayCommand |
|
{ |
|
logLevel ??= LogLevel.Error; |
|
|
|
return command.WithConditionalErrorHandler(e => |
|
{ |
|
Logger.Log(logLevel, e, "Error executing command"); |
|
notificationService.ShowPersistent("Error", $"[{e.GetType().Name}] {e.Message}"); |
|
}); |
|
} |
|
}
|
|
|