Skip to content

Commit

Permalink
Merge pull request #21 from brminnick/Release-v3.1.0
Browse files Browse the repository at this point in the history
Release v3.1.0
  • Loading branch information
TheCodeTraveler authored Aug 28, 2019
2 parents 587496f + eac68e1 commit 3a9522e
Show file tree
Hide file tree
Showing 38 changed files with 438 additions and 342 deletions.
21 changes: 12 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Available on NuGet: https://www.nuget.org/packages/AsyncAwaitBestPractices/
### AsyncAwaitBestPractices.MVVM

- Available on NuGet: https://www.nuget.org/packages/AsyncAwaitBestPractices.MVVM/
- Add to any project supporting .NET Standard 2.0
- Add to any project supporting .NET Standard 1.0

## Why Do I Need This?

Expand Down Expand Up @@ -152,12 +152,16 @@ void InitializeSafeFireAndForget()
SafeFireAndForgetExtensions.SetDefaultExceptionHandling(ex => Console.WriteLine(ex));
}

void UninitializeSafeFireAndForget()
{
// Remove default exception handling
SafeFireAndForgetExtensions.RemoveDefaultExceptionHandling()
}

void HandleButtonTapped(object sender, EventArgs e)
{
// Allows the async Task method to safely run on a different thread while not awaiting its completion
// onException: If a WebException is thrown, print its StatusCode to the Console. **Note**: If a non-WebException is thrown, it will not be handled by `onException`
// Because we set `SetDefaultExceptionHandling` in `void InitializeSafeFireAndForget()`, the entire exception will also be printed to the Console
ExampleAsyncMethod().SafeFireAndForget<WebException>(onException: ex =>
{
Expand All @@ -166,7 +170,6 @@ void HandleButtonTapped(object sender, EventArgs e)
});

// HandleButtonTapped continues execution here while `ExampleAsyncMethod()` is running on a different thread
// ...
}

async Task ExampleAsyncMethod()
Expand All @@ -193,7 +196,7 @@ public event EventHandler CanExecuteChanged
remove => _weakEventManager.RemoveEventHandler(value);
}

public void OnCanExecuteChanged() => _canExecuteChangedEventManager.HandleEvent(this, EventArgs.Empty, nameof(CanExecuteChanged));
void OnCanExecuteChanged() => _canExecuteChangedEventManager.HandleEvent(this, EventArgs.Empty, nameof(CanExecuteChanged));
```

#### Using `Delegate`
Expand All @@ -207,7 +210,7 @@ public event PropertyChangedEventHandler PropertyChanged
remove => _propertyChangedEventManager.RemoveEventHandler(value);
}

public void OnPropertyChanged([CallerMemberName]string propertyName = "") => _propertyChangedEventManager.HandleEvent(this, new PropertyChangedEventArgs(propertyName), nameof(PropertyChanged));
void OnPropertyChanged([CallerMemberName]string propertyName = "") => _propertyChangedEventManager.HandleEvent(this, new PropertyChangedEventArgs(propertyName), nameof(PropertyChanged));
```

#### Using `Action`
Expand All @@ -221,7 +224,7 @@ public event Action ActionEvent
remove => _weakActionEventManager.RemoveEventHandler(value);
}

public void OnActionEvent(string message) => _weakActionEventManager.HandleEvent(message, nameof(ActionEvent));
void OnActionEvent(string message) => _weakActionEventManager.HandleEvent(message, nameof(ActionEvent));
```

### `WeakEventManager<T>`
Expand All @@ -238,7 +241,7 @@ public event EventHandler<string> ErrorOcurred
remove => _errorOcurredEventManager.RemoveEventHandler(value);
}

public void OnErrorOcurred(string message) => _errorOcurredEventManager.HandleEvent(this, message, nameof(ErrorOcurred));
void OnErrorOcurred(string message) => _errorOcurredEventManager.HandleEvent(this, message, nameof(ErrorOcurred));
```

#### Using `Action<T>`
Expand All @@ -252,7 +255,7 @@ public event Action<string> ActionEvent
remove => _weakActionEventManager.RemoveEventHandler(value);
}

public void OnActionEvent(string message) => _weakActionEventManager.HandleEvent(message, nameof(ActionEvent));
void OnActionEvent(string message) => _weakActionEventManager.HandleEvent(message, nameof(ActionEvent));
```

## AsyncAwaitBestPractices.MVVM
Expand Down
22 changes: 10 additions & 12 deletions Src/AsyncAwaitBestPractices.MVVM.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata minClientVersion="2.5">
<id>AsyncAwaitBestPractices.MVVM</id>
<version>3.0.0</version>
<version>3.1.0</version>
<title>Task Extensions for MVVM</title>
<authors>Brandon Minnick, John Thiriet</authors>
<owners>Brandon Minnick</owners>
Expand All @@ -14,23 +14,21 @@
<summary>Includes AsyncCommand and IAsyncCommand which allows ICommand to safely be used asynchronously with Task.</summary>
<tags>task,fire and forget, threading, extensions, system.threading.tasks,async,await</tags>
<dependencies>
<dependency id="AsyncAwaitBestPractices" version="3.0.0" />
<dependency id="AsyncAwaitBestPractices" version="3.1.0" />
</dependencies>
<releaseNotes>
New In This Release:
- Added support for `event Action` and `event Action&lt;T&gt;`
- Added support for `.SafeFireAndForget&lt;TException&gt;()`
- Added `SafeFireAndForgetExtensions.SetDefaultExceptionHandling(Action&lt;Exception&gt; onException)` to set a default action for every call to `SafeFireAndForget`
- Added `SafeFireAndForgetExtensions.Initialize(bool shouldAlwaysRethrowException = false)`. When set to `true` will rethrow every exception caught by `SafeFireAndForget`. Warning: `SafeFireAndForgetExtensions.Initialize(true)` is only recommended for DEBUG environments.
- Added support for `Exception innerException` to `InvalidCommandParameterException`
- Breaking Change: Changed default value to `continueOnCapturedContext = false`. This improves performance by not requiring a context switch when `.SafeFireAndForget()` and `IAsyncCommand` have completed.
- Added `SafeFireAndForgetExtensions.RemoveDefaultExceptionHandling`
- Fixed InvalidHandleEventException bug when using the incorrect overload for `WeakEventManager&lt;T&gt;.HandleEvent`
- Fixed `NullReferenceException` when calling `ICommand.Execute`
- Improved .NET Standard Dependency to .NET Standard 1.0
</releaseNotes>
<repository type="git" url="https://github.com/brminnick/AsyncAwaitBestPractices.git" branch="master" commit="e768a96e2ceddc7644eee54d60cbd9343ca857b7" />
<repository type="git" url="https://github.com/brminnick/AsyncAwaitBestPractices.git" branch="Release-v3.1.0" commit="c13174438950d3e1adc54d2911dc900df877a4de" />
<copyright>Copyright (c) 2018 Brandon Minnick</copyright>
</metadata>
<files>
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard2.0\AsyncAwaitBestPractices.MVVM.pdb" target="lib\netstandard2.0\AsyncAwaitBestPractices.MVVM.pdb" />
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard2.0\AsyncAwaitBestPractices.MVVM.dll" target="lib\netstandard2.0\AsyncAwaitBestPractices.MVVM.dll" />
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard2.0\AsyncAwaitBestPractices.MVVM.xml" target="lib\netstandard2.0\AsyncAwaitBestPractices.MVVM.xml" />
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard1.0\AsyncAwaitBestPractices.MVVM.pdb" target="lib\netstandard1.0\AsyncAwaitBestPractices.MVVM.pdb" />
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard1.0\AsyncAwaitBestPractices.MVVM.dll" target="lib\netstandard1.0\AsyncAwaitBestPractices.MVVM.dll" />
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard1.0\AsyncAwaitBestPractices.MVVM.xml" target="lib\netstandard1.0\AsyncAwaitBestPractices.MVVM.xml" />
</files>
</package>
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<TargetFramework>netstandard1.0</TargetFramework>
<LangVersion>8.0</LangVersion>
<Nullable>enable</Nullable>
<NullableContextOptions>enable</NullableContextOptions>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
Expand Down
61 changes: 29 additions & 32 deletions Src/AsyncAwaitBestPractices.MVVM/AsyncCommand.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows.Input;

Expand All @@ -9,15 +10,12 @@ namespace AsyncAwaitBestPractices.MVVM
/// </summary>
public sealed class AsyncCommand<T> : IAsyncCommand<T>
{
#region Constant Fields
readonly Func<T, Task> _execute;
readonly Func<object, bool> _canExecute;
readonly Action<Exception> _onException;
readonly Func<object?, bool> _canExecute;
readonly Action<Exception>? _onException;
readonly bool _continueOnCapturedContext;
readonly WeakEventManager _weakEventManager = new WeakEventManager();
#endregion

#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="T:TaskExtensions.MVVM.AsyncCommand`1"/> class.
/// </summary>
Expand All @@ -26,18 +24,16 @@ public sealed class AsyncCommand<T> : IAsyncCommand<T>
/// <param name="onException">If an exception is thrown in the Task, <c>onException</c> will execute. If onException is null, the exception will be re-thrown</param>
/// <param name="continueOnCapturedContext">If set to <c>true</c> continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to <c>false</c> continue on a different context; this will allow the Synchronization Context to continue on a different thread</param>
public AsyncCommand(Func<T, Task> execute,
Func<object, bool> canExecute = null,
Action<Exception> onException = null,
Func<object?, bool>? canExecute = null,
Action<Exception>? onException = null,
bool continueOnCapturedContext = false)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute), $"{nameof(execute)} cannot be null");
_canExecute = canExecute ?? (_ => true);
_onException = onException;
_continueOnCapturedContext = continueOnCapturedContext;
}
#endregion

#region Events
/// <summary>
/// Occurs when changes occur that affect whether or not the command should execute
/// </summary>
Expand All @@ -46,15 +42,13 @@ public event EventHandler CanExecuteChanged
add => _weakEventManager.AddEventHandler(value);
remove => _weakEventManager.RemoveEventHandler(value);
}
#endregion

#region Methods
/// <summary>
/// Determines whether the command can execute in its current state
/// </summary>
/// <returns><c>true</c>, if this command can be executed; otherwise, <c>false</c>.</returns>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
public bool CanExecute(object parameter) => _canExecute(parameter);
public bool CanExecute(object? parameter) => _canExecute(parameter);

/// <summary>
/// Raises the CanExecuteChanged event.
Expand All @@ -70,30 +64,38 @@ public event EventHandler CanExecuteChanged

void ICommand.Execute(object parameter)
{
if (parameter is T validParameter)
ExecuteAsync(validParameter).SafeFireAndForget(_continueOnCapturedContext, _onException);
else if (parameter is null && !typeof(T).IsValueType)
ExecuteAsync((T)parameter).SafeFireAndForget(_continueOnCapturedContext, _onException);
else
throw new InvalidCommandParameterException(typeof(T), parameter.GetType());
switch (parameter)
{
case T validParameter:
ExecuteAsync(validParameter).SafeFireAndForget(_continueOnCapturedContext, _onException);
break;

#pragma warning disable CS8601 //Possible null reference assignment
case null when !typeof(T).GetTypeInfo().IsValueType:
ExecuteAsync((T)parameter).SafeFireAndForget(_continueOnCapturedContext, _onException);
break;
#pragma warning restore CS8601

case null:
throw new InvalidCommandParameterException(typeof(T));

default:
throw new InvalidCommandParameterException(typeof(T), parameter.GetType());
}
}
#endregion
}

/// <summary>
/// An implmentation of IAsyncCommand. Allows Commands to safely be used asynchronously with Task.
/// </summary>
public sealed class AsyncCommand : IAsyncCommand
{
#region Constant Fields
readonly Func<Task> _execute;
readonly Func<object, bool> _canExecute;
readonly Action<Exception> _onException;
readonly Func<object?, bool> _canExecute;
readonly Action<Exception>? _onException;
readonly bool _continueOnCapturedContext;
readonly WeakEventManager _weakEventManager = new WeakEventManager();
#endregion

#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="T:TaskExtensions.MVVM.AsyncCommand`1"/> class.
/// </summary>
Expand All @@ -102,18 +104,16 @@ public sealed class AsyncCommand : IAsyncCommand
/// <param name="onException">If an exception is thrown in the Task, <c>onException</c> will execute. If onException is null, the exception will be re-thrown</param>
/// <param name="continueOnCapturedContext">If set to <c>true</c> continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to <c>false</c> continue on a different context; this will allow the Synchronization Context to continue on a different thread</param>
public AsyncCommand(Func<Task> execute,
Func<object, bool> canExecute = null,
Action<Exception> onException = null,
Func<object?, bool>? canExecute = null,
Action<Exception>? onException = null,
bool continueOnCapturedContext = false)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute), $"{nameof(execute)} cannot be null");
_canExecute = canExecute ?? (_ => true);
_onException = onException;
_continueOnCapturedContext = continueOnCapturedContext;
}
#endregion

#region Events
/// <summary>
/// Occurs when changes occur that affect whether or not the command should execute
/// </summary>
Expand All @@ -122,15 +122,13 @@ public event EventHandler CanExecuteChanged
add => _weakEventManager.AddEventHandler(value);
remove => _weakEventManager.RemoveEventHandler(value);
}
#endregion

#region Methods
/// <summary>
/// Determines whether the command can execute in its current state
/// </summary>
/// <returns><c>true</c>, if this command can be executed; otherwise, <c>false</c>.</returns>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
public bool CanExecute(object parameter) => _canExecute(parameter);
public bool CanExecute(object? parameter) => _canExecute(parameter);

/// <summary>
/// Raises the CanExecuteChanged event.
Expand All @@ -144,6 +142,5 @@ public event EventHandler CanExecuteChanged
public Task ExecuteAsync() => _execute();

void ICommand.Execute(object parameter) => _execute().SafeFireAndForget(_continueOnCapturedContext, _onException);
#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ namespace AsyncAwaitBestPractices.MVVM
/// <summary>
/// Represents errors that occur during IAsyncCommand execution.
/// </summary>
class InvalidCommandParameterException : Exception
public class InvalidCommandParameterException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="T:TaskExtensions.MVVM.InvalidCommandParameterException"/> class.
/// </summary>
/// <param name="excpectedType">Excpected parameter type for AsyncCommand.Execute.</param>
/// <param name="actualType">Actual parameter type for AsyncCommand.Execute.</param>
/// <param name="innerException">Inner Exception</param>
public InvalidCommandParameterException(Type excpectedType, Type actualType, Exception innerException) : this(CreateErrorMessage(excpectedType, actualType), innerException)
public InvalidCommandParameterException(Type excpectedType, Type actualType, Exception innerException) : base(CreateErrorMessage(excpectedType, actualType), innerException)
{

}
Expand All @@ -23,39 +23,32 @@ public InvalidCommandParameterException(Type excpectedType, Type actualType, Exc
/// </summary>
/// <param name="excpectedType">Excpected parameter type for AsyncCommand.Execute.</param>
/// <param name="actualType">Actual parameter type for AsyncCommand.Execute.</param>
public InvalidCommandParameterException(Type excpectedType, Type actualType) : this(CreateErrorMessage(excpectedType, actualType))
public InvalidCommandParameterException(Type excpectedType, Type actualType) : base(CreateErrorMessage(excpectedType, actualType))
{

}

/// <summary>
/// Initializes a new instance of the <see cref="T:TaskExtensions.MVVM.InvalidCommandParameterException"/> class.
/// </summary>
/// <param name="message">Exception Message</param>
/// <param name="excpectedType">Excpected parameter type for AsyncCommand.Execute.</param>
/// <param name="innerException">Inner Exception</param>
public InvalidCommandParameterException(string message, Exception innerException) : base(message, innerException)
public InvalidCommandParameterException(Type excpectedType, Exception innerException) : base(CreateErrorMessage(excpectedType), innerException)
{

}

/// <summary>
/// Initializes a new instance of the <see cref="T:TaskExtensions.MVVM.InvalidCommandParameterException"/> class.
/// </summary>
/// <param name="message">Exception Message</param>
public InvalidCommandParameterException(string message) : base(message)
/// <param name="excpectedType">Excpected parameter type for AsyncCommand.Execute.</param>
public InvalidCommandParameterException(Type excpectedType) : base(CreateErrorMessage(excpectedType))
{

}

/// <summary>
/// Initializes a new instance of the <see cref="T:TaskExtensions.MVVM.InvalidCommandParameterException"/> class.
/// </summary>
public InvalidCommandParameterException()
{

}
static string CreateErrorMessage(Type excpectedType) => $"Invalid type for parameter. Expected Type {excpectedType}";

static string CreateErrorMessage(Type excpectedType, Type actualType) =>
$"Invalid type for parameter. Expected Type {excpectedType}, but received Type {actualType}";
static string CreateErrorMessage(Type excpectedType, Type actualType) => $"Invalid type for parameter. Expected Type {excpectedType}, but received Type {actualType}";
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<LangVersion>latest</LangVersion>
<TargetFramework>netcoreapp3.0</TargetFramework>
<LangVersion>8.0</LangVersion>
<Nullable>enable</Nullable>
<NullableContextOptions>enable</NullableContextOptions>
<IsPackable>false</IsPackable>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="nunit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.14.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
</ItemGroup>
<ItemGroup>
Expand Down
Loading

0 comments on commit 3a9522e

Please sign in to comment.