diff --git a/Docs/releases.md b/Docs/releases.md
index a05568a2..426fd415 100644
--- a/Docs/releases.md
+++ b/Docs/releases.md
@@ -1,7 +1,12 @@
# Releases
+### New in 3.6
+ * Ensure synchronous effects are executed synchronously ([#75](https://github.com/mrpmorris/fluxor/issues/75)) -
+ Reverts changes for [(#74) Endless loop redirects](https://github.com/mrpmorris/Fluxor/issues/74) as
+ these no longer occur.
+
### New in 3.5
- * Bug fix for ([#74](https://github.com/mrpmorris/bfluxor/issues/74)) - Detect endless loop redirects
+ * Bug fix for ([#74](https://github.com/mrpmorris/Fluxor/issues/74)) - Handle endless loop redirects caused by Routing middleware.
### New in 3.4
* **Breaking change**: `FluxorException` is now an `abstract` class.
diff --git a/Source/Fluxor-NoTutorials.sln b/Source/Fluxor-NoTutorials.sln
new file mode 100644
index 00000000..38995848
--- /dev/null
+++ b/Source/Fluxor-NoTutorials.sln
@@ -0,0 +1,65 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.28809.33
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fluxor", "Fluxor\Fluxor.csproj", "{863909D3-7E81-4240-8C0A-6F57768D28FF}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fluxor.Blazor.Web", "Fluxor.Blazor.Web\Fluxor.Blazor.Web.csproj", "{6289FEA3-4F01-4100-BB4B-6F8938203886}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fluxor.Blazor.Web.ReduxDevTools", "Fluxor.Blazor.Web.ReduxDevTools\Fluxor.Blazor.Web.ReduxDevTools.csproj", "{E33D5229-FFD7-410B-99E4-5FA2E02CCAFE}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|iPhone = Debug|iPhone
+ Debug|iPhoneSimulator = Debug|iPhoneSimulator
+ Release|Any CPU = Release|Any CPU
+ Release|iPhone = Release|iPhone
+ Release|iPhoneSimulator = Release|iPhoneSimulator
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {863909D3-7E81-4240-8C0A-6F57768D28FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {863909D3-7E81-4240-8C0A-6F57768D28FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {863909D3-7E81-4240-8C0A-6F57768D28FF}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {863909D3-7E81-4240-8C0A-6F57768D28FF}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {863909D3-7E81-4240-8C0A-6F57768D28FF}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {863909D3-7E81-4240-8C0A-6F57768D28FF}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {863909D3-7E81-4240-8C0A-6F57768D28FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {863909D3-7E81-4240-8C0A-6F57768D28FF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {863909D3-7E81-4240-8C0A-6F57768D28FF}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {863909D3-7E81-4240-8C0A-6F57768D28FF}.Release|iPhone.Build.0 = Release|Any CPU
+ {863909D3-7E81-4240-8C0A-6F57768D28FF}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {863909D3-7E81-4240-8C0A-6F57768D28FF}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {6289FEA3-4F01-4100-BB4B-6F8938203886}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6289FEA3-4F01-4100-BB4B-6F8938203886}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6289FEA3-4F01-4100-BB4B-6F8938203886}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {6289FEA3-4F01-4100-BB4B-6F8938203886}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {6289FEA3-4F01-4100-BB4B-6F8938203886}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {6289FEA3-4F01-4100-BB4B-6F8938203886}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {6289FEA3-4F01-4100-BB4B-6F8938203886}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6289FEA3-4F01-4100-BB4B-6F8938203886}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6289FEA3-4F01-4100-BB4B-6F8938203886}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {6289FEA3-4F01-4100-BB4B-6F8938203886}.Release|iPhone.Build.0 = Release|Any CPU
+ {6289FEA3-4F01-4100-BB4B-6F8938203886}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {6289FEA3-4F01-4100-BB4B-6F8938203886}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {E33D5229-FFD7-410B-99E4-5FA2E02CCAFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E33D5229-FFD7-410B-99E4-5FA2E02CCAFE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E33D5229-FFD7-410B-99E4-5FA2E02CCAFE}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {E33D5229-FFD7-410B-99E4-5FA2E02CCAFE}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {E33D5229-FFD7-410B-99E4-5FA2E02CCAFE}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {E33D5229-FFD7-410B-99E4-5FA2E02CCAFE}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {E33D5229-FFD7-410B-99E4-5FA2E02CCAFE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E33D5229-FFD7-410B-99E4-5FA2E02CCAFE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E33D5229-FFD7-410B-99E4-5FA2E02CCAFE}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {E33D5229-FFD7-410B-99E4-5FA2E02CCAFE}.Release|iPhone.Build.0 = Release|Any CPU
+ {E33D5229-FFD7-410B-99E4-5FA2E02CCAFE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {E33D5229-FFD7-410B-99E4-5FA2E02CCAFE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {B1F2E0DF-C651-48A3-83E3-3B77D34EC3A2}
+ EndGlobalSection
+EndGlobal
diff --git a/Source/Fluxor.Blazor.Web.ReduxDevTools/Fluxor.Blazor.Web.ReduxDevTools.csproj b/Source/Fluxor.Blazor.Web.ReduxDevTools/Fluxor.Blazor.Web.ReduxDevTools.csproj
index cecbc20d..569d2e7e 100644
--- a/Source/Fluxor.Blazor.Web.ReduxDevTools/Fluxor.Blazor.Web.ReduxDevTools.csproj
+++ b/Source/Fluxor.Blazor.Web.ReduxDevTools/Fluxor.Blazor.Web.ReduxDevTools.csproj
@@ -3,7 +3,7 @@
netstandard2.1
3.0
- 3.5.0
+ 3.6.0
Peter Morris
ReduxDevTools for Fluxor Blazor (Web)
@@ -17,8 +17,8 @@
git
Redux Flux DotNet CSharp Blazor RazorComponents ReduxDevTools
true
- 3.5.0.0
- 3.5.0.0
+ 3.6.0.0
+ 3.6.0.0
true
MrPMorris.snk
diff --git a/Source/Fluxor.Blazor.Web/Fluxor.Blazor.Web.csproj b/Source/Fluxor.Blazor.Web/Fluxor.Blazor.Web.csproj
index 57101297..cce43bc9 100644
--- a/Source/Fluxor.Blazor.Web/Fluxor.Blazor.Web.csproj
+++ b/Source/Fluxor.Blazor.Web/Fluxor.Blazor.Web.csproj
@@ -5,7 +5,7 @@
3.0
Peter Morris
- 3.5.0
+ 3.6.0
A zero boilerplate Redux/Flux framework for Blazor
Peter Morris
MIT
@@ -17,8 +17,8 @@
Redux Flux DotNet CSharp Blazor RazorComponents
Fluxor for Blazor (Web)
true
- 3.5.0.0
- 3.5.0.0
+ 3.6.0.0
+ 3.6.0.0
true
MrPMorris.snk
diff --git a/Source/Fluxor.Blazor.Web/Middlewares/Routing/RoutingMiddleware.cs b/Source/Fluxor.Blazor.Web/Middlewares/Routing/RoutingMiddleware.cs
index f528ecff..646e6613 100644
--- a/Source/Fluxor.Blazor.Web/Middlewares/Routing/RoutingMiddleware.cs
+++ b/Source/Fluxor.Blazor.Web/Middlewares/Routing/RoutingMiddleware.cs
@@ -1,7 +1,5 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;
-using System;
-using System.Linq;
using System.Threading.Tasks;
namespace Fluxor.Blazor.Web.Middlewares.Routing
@@ -12,11 +10,9 @@ namespace Fluxor.Blazor.Web.Middlewares.Routing
///
internal class RoutingMiddleware : Middleware
{
- private readonly TimeSpan LoopRedirectDetectionWindow;
private readonly NavigationManager NavigationManager;
private readonly IFeature Feature;
private IStore Store;
- private (string Url, DateTime NavigationTime)[] PreviousNavigations;
///
/// Creates a new instance of the routing middleware
@@ -27,8 +23,6 @@ public RoutingMiddleware(NavigationManager navigationManager, IFeature();
NavigationManager.LocationChanged += LocationChanged;
}
@@ -50,28 +44,8 @@ protected override void OnInternalMiddlewareChangeEnding()
private void LocationChanged(object sender, LocationChangedEventArgs e)
{
- if (Store != null
- && !IsInsideMiddlewareChange
- && e.Location != Feature.State.Uri
- && !LoopedRedirectDetected(e))
- {
+ if (Store != null && !IsInsideMiddlewareChange && e.Location != Feature.State.Uri)
Store.Dispatch(new GoAction(e.Location));
- }
- }
-
- private bool LoopedRedirectDetected(LocationChangedEventArgs e)
- {
- if (e.IsNavigationIntercepted)
- return false;
-
- DateTime cutoffTime = DateTime.UtcNow.Subtract(LoopRedirectDetectionWindow);
- PreviousNavigations =
- PreviousNavigations
- .Where(x => x.NavigationTime >= cutoffTime)
- .Append((e.Location, DateTime.UtcNow))
- .ToArray();
-
- return (PreviousNavigations.Count(x => x.Url == e.Location) >= 2);
}
}
}
diff --git a/Source/Fluxor/Fluxor.csproj b/Source/Fluxor/Fluxor.csproj
index 95569362..87fe4a81 100644
--- a/Source/Fluxor/Fluxor.csproj
+++ b/Source/Fluxor/Fluxor.csproj
@@ -2,7 +2,7 @@
netstandard2.1;netcoreapp3.1
- 3.5.0
+ 3.6.0
true
Peter Morris
@@ -16,8 +16,8 @@
MIT
git
- 3.5.0.0
- 3.5.0.0
+ 3.6.0.0
+ 3.6.0.0
true
MrPMorris.snk
false
diff --git a/Source/Fluxor/Store.cs b/Source/Fluxor/Store.cs
index 6b249e8c..f3a3a0de 100644
--- a/Source/Fluxor/Store.cs
+++ b/Source/Fluxor/Store.cs
@@ -1,5 +1,4 @@
-using Fluxor.Exceptions;
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@@ -184,29 +183,49 @@ private void EndMiddlewareChange(IDisposable[] disposables)
private void TriggerEffects(object action)
{
- Task.Run(async() =>
+ var recordedExceptions = new List();
+ var effectsToExecute = Effects.Where(x => x.ShouldReactToAction(action));
+ var executedEffects = new List();
+
+ Action collectExceptions = e =>
{
- IEnumerable exceptions = Array.Empty();
+ if (e is AggregateException aggregateException)
+ recordedExceptions.AddRange(aggregateException.Flatten().InnerExceptions);
+ else
+ recordedExceptions.Add(e);
+ };
+ // Execute all tasks. Some will execute synchronously and complete immediately,
+ // so we need to catch their exceptions in the loop so they don't prevent
+ // other effects from executing.
+ // It's then up to the UI to decide if any of those exceptions should cause
+ // the app to terminate or not.
+ foreach (IEffect effect in effectsToExecute)
+ {
try
{
- var triggeredEffects = Effects
- .Where(x => x.ShouldReactToAction(action))
- .Select(x => x.HandleAsync(action, this))
- .ToArray();
-
- await Task.WhenAll(triggeredEffects);
+ executedEffects.Add(effect.HandleAsync(action, this));
}
- catch (AggregateException e)
+ catch (Exception e)
+ {
+ collectExceptions(e);
+ }
+ }
+
+ Task.Run(async() =>
+ {
+ try
{
- exceptions = e.Flatten().InnerExceptions;
+ await Task.WhenAll(executedEffects);
}
catch (Exception e)
{
- exceptions = new Exception[] { e };
+ collectExceptions(e);
}
- foreach (Exception exception in exceptions)
+ // Let the UI decide if it wishes to deal with any unhandled exceptions.
+ // By default it should throw the exception if it is not handled.
+ foreach (Exception exception in recordedExceptions)
UnhandledException?.Invoke(this, new Exceptions.UnhandledExceptionEventArgs(exception));
});
}
diff --git a/Tests/Fluxor.UnitTests/StoreTests/Dispatch.cs b/Tests/Fluxor.UnitTests/StoreTests/Dispatch.cs
index 7c84c3a4..45ac1c04 100644
--- a/Tests/Fluxor.UnitTests/StoreTests/Dispatch.cs
+++ b/Tests/Fluxor.UnitTests/StoreTests/Dispatch.cs
@@ -125,5 +125,33 @@ public async Task WhenCalled_ThenTriggersOnlyEffectsThatHandleTheDispatchedActio
mockIncompatibleEffect.Verify(x => x.HandleAsync(action, It.IsAny()), Times.Never);
mockCompatibleEffect.Verify(x => x.HandleAsync(action, It.IsAny()), Times.Once);
}
+
+ [Fact]
+ public async Task WhenSynchronousEffectThrowsException_ThenStillExecutesSubsequentEffects()
+ {
+ var subject = new TestStore();
+ var action = new object();
+
+ var mockSynchronousEffectThatThrows = new Mock();
+ mockSynchronousEffectThatThrows
+ .Setup(x => x.ShouldReactToAction(action))
+ .Returns(true);
+ mockSynchronousEffectThatThrows
+ .Setup(x => x.HandleAsync(action, subject))
+ .ThrowsAsync(new NotImplementedException());
+
+ var mockEffectThatFollows = new Mock();
+ mockEffectThatFollows
+ .Setup(x => x.ShouldReactToAction(action))
+ .Returns(true);
+
+ await subject.InitializeAsync();
+ subject.AddEffect(mockSynchronousEffectThatThrows.Object);
+ subject.AddEffect(mockEffectThatFollows.Object);
+ subject.Dispatch(action);
+
+ mockSynchronousEffectThatThrows.Verify(x => x.HandleAsync(action, subject));
+ mockEffectThatFollows.Verify(x => x.HandleAsync(action, subject));
+ }
}
}
\ No newline at end of file
diff --git a/Tests/Fluxor.UnitTests/SupportFiles/EffectThatThrowsAggregateException.cs b/Tests/Fluxor.UnitTests/SupportFiles/EffectThatThrowsAggregateException.cs
index 58ff4eee..8e086d46 100644
--- a/Tests/Fluxor.UnitTests/SupportFiles/EffectThatThrowsAggregateException.cs
+++ b/Tests/Fluxor.UnitTests/SupportFiles/EffectThatThrowsAggregateException.cs
@@ -11,7 +11,7 @@ protected override async Task HandleAsync(ThrowAggregateExceptionAction action,
var exception2 = new InvalidCastException("Second embedded exception");
var exception3 = new InvalidProgramException("Third embedded exception");
- await Task.Delay(10).ConfigureAwait(false);
+ await Task.Delay(100);
try
{
throw new AggregateException(
diff --git a/Tests/Fluxor.UnitTests/SupportFiles/EffectThatThrowsSimpleException.cs b/Tests/Fluxor.UnitTests/SupportFiles/EffectThatThrowsSimpleException.cs
index 4bfb22e4..a72a898e 100644
--- a/Tests/Fluxor.UnitTests/SupportFiles/EffectThatThrowsSimpleException.cs
+++ b/Tests/Fluxor.UnitTests/SupportFiles/EffectThatThrowsSimpleException.cs
@@ -7,7 +7,7 @@ public class EffectThatThrowsSimpleException : Effect