From 91408414d060a0711bb74a80293cb181f367ff56 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Sat, 15 Jan 2022 01:11:40 -0500 Subject: [PATCH 01/17] Core updates --- .../CounterApp/CounterApp/CounterApp.csproj | 11 +- .../CounterApp/CounterApp/Features/Counter.cs | 64 ++++---- .../CounterApp/Features/WeatherForecast.cs | 32 ++-- .../CounterApp/Pages/FetchData.razor | 7 +- samples/CounterApp/CounterApp/Program.cs | 21 ++- .../Shared/FluentValidationSummary.razor | 3 +- .../CounterApp/Shared/MainLayout.razor | 4 +- .../ReduxDevTools/ReduxDevToolsInterop.cs | 1 - src/StateR.Blazor/StatorComponentBase.cs | 12 +- .../AsyncLogic/StartupExtensions.cs | 2 +- .../FluentValidation/StartupExtensions.cs | 4 +- .../ActionHandlers/ActionHandlersManager.cs | 29 ---- .../Hooks/ActionHandlerHooksCollection.cs | 28 ---- .../Hooks/IActionHandlerHooksCollection.cs | 7 - .../ActionHandlers/Hooks/IAfterActionHook.cs | 6 - .../ActionHandlers/Hooks/IBeforeActionHook.cs | 6 - src/StateR/ActionHandlers/IActionHandler.cs | 7 - .../ActionHandlers/IActionHandlersManager.cs | 3 - .../AfterEffects/AfterEffectsManager.cs | 29 ---- .../Hooks/AfterEffectHooksCollection.cs | 29 ---- .../Hooks/IAfterAfterEffectHook.cs | 7 - .../Hooks/IAfterEffectHooksCollection.cs | 8 - .../Hooks/IBeforeAfterEffectHook.cs | 7 - src/StateR/AfterEffects/IAfterEffects.cs | 7 - .../AfterEffects/IAfterEffectsManager.cs | 3 - src/StateR/DispatchContext.cs | 12 +- src/StateR/DispatchContextFactory.cs | 7 +- src/StateR/Dispatcher.cs | 50 +++--- src/StateR/IAction.cs | 2 +- src/StateR/IDispatchContext.cs | 5 +- src/StateR/IDispatchContextFactory.cs | 4 +- src/StateR/IDispatchManager.cs | 5 +- src/StateR/IDispatcher.cs | 5 +- src/StateR/IStatorBuilder.cs | 4 + .../Hooks/IAfterInterceptorHook.cs | 6 - .../Hooks/IBeforeInterceptorHook.cs | 6 - .../Hooks/IInterceptorsHooksCollection.cs | 7 - .../Hooks/InterceptorsHooksCollection.cs | 28 ---- src/StateR/Interceptors/IInterceptor.cs | 7 - .../Interceptors/IInterceptorsManager.cs | 3 - .../Interceptors/InterceptorsManager.cs | 29 ---- src/StateR/Internal/StatorBuilder.cs | 4 + .../Internal/TypeScannerBuilderExtensions.cs | 30 +--- src/StateR/Pipeline/IActionFilter.cs | 13 ++ src/StateR/Pipeline/IActionFilterFactory.cs | 26 ++++ src/StateR/StatorStartupExtensions.cs | 142 +++++++++--------- src/StateR/Store.cs | 9 +- src/StateR/Updaters/Hooks/IAfterUpdateHook.cs | 8 - .../Updaters/Hooks/IBeforeUpdateHook.cs | 8 - .../Updaters/Hooks/IUpdateHooksCollection.cs | 11 -- .../Updaters/Hooks/UpdateHooksCollection.cs | 32 ---- src/StateR/Updaters/IUpdater.cs | 2 +- src/StateR/Updaters/UpdaterActionHandler.cs | 40 ----- src/StateR/Updaters/UpdaterMiddleware.cs | 34 +++++ .../Reducers/UpdaterActionHandlerTest.cs | 2 +- 55 files changed, 309 insertions(+), 569 deletions(-) delete mode 100644 src/StateR/ActionHandlers/ActionHandlersManager.cs delete mode 100644 src/StateR/ActionHandlers/Hooks/ActionHandlerHooksCollection.cs delete mode 100644 src/StateR/ActionHandlers/Hooks/IActionHandlerHooksCollection.cs delete mode 100644 src/StateR/ActionHandlers/Hooks/IAfterActionHook.cs delete mode 100644 src/StateR/ActionHandlers/Hooks/IBeforeActionHook.cs delete mode 100644 src/StateR/ActionHandlers/IActionHandler.cs delete mode 100644 src/StateR/ActionHandlers/IActionHandlersManager.cs delete mode 100644 src/StateR/AfterEffects/AfterEffectsManager.cs delete mode 100644 src/StateR/AfterEffects/Hooks/AfterEffectHooksCollection.cs delete mode 100644 src/StateR/AfterEffects/Hooks/IAfterAfterEffectHook.cs delete mode 100644 src/StateR/AfterEffects/Hooks/IAfterEffectHooksCollection.cs delete mode 100644 src/StateR/AfterEffects/Hooks/IBeforeAfterEffectHook.cs delete mode 100644 src/StateR/AfterEffects/IAfterEffects.cs delete mode 100644 src/StateR/AfterEffects/IAfterEffectsManager.cs delete mode 100644 src/StateR/Interceptors/Hooks/IAfterInterceptorHook.cs delete mode 100644 src/StateR/Interceptors/Hooks/IBeforeInterceptorHook.cs delete mode 100644 src/StateR/Interceptors/Hooks/IInterceptorsHooksCollection.cs delete mode 100644 src/StateR/Interceptors/Hooks/InterceptorsHooksCollection.cs delete mode 100644 src/StateR/Interceptors/IInterceptor.cs delete mode 100644 src/StateR/Interceptors/IInterceptorsManager.cs delete mode 100644 src/StateR/Interceptors/InterceptorsManager.cs create mode 100644 src/StateR/Pipeline/IActionFilter.cs create mode 100644 src/StateR/Pipeline/IActionFilterFactory.cs delete mode 100644 src/StateR/Updaters/Hooks/IAfterUpdateHook.cs delete mode 100644 src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs delete mode 100644 src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs delete mode 100644 src/StateR/Updaters/Hooks/UpdateHooksCollection.cs delete mode 100644 src/StateR/Updaters/UpdaterActionHandler.cs create mode 100644 src/StateR/Updaters/UpdaterMiddleware.cs diff --git a/samples/CounterApp/CounterApp/CounterApp.csproj b/samples/CounterApp/CounterApp/CounterApp.csproj index bb327bf..98b8158 100644 --- a/samples/CounterApp/CounterApp/CounterApp.csproj +++ b/samples/CounterApp/CounterApp/CounterApp.csproj @@ -5,14 +5,21 @@ + + + + + + + + + - - diff --git a/samples/CounterApp/CounterApp/Features/Counter.cs b/samples/CounterApp/CounterApp/Features/Counter.cs index 34f30ca..e9ce5cc 100644 --- a/samples/CounterApp/CounterApp/Features/Counter.cs +++ b/samples/CounterApp/CounterApp/Features/Counter.cs @@ -1,19 +1,13 @@ -using FluentValidation; +//using FluentValidation; using StateR; -using StateR.AfterEffects; -using StateR.Blazor.Persistance; -using ForEvolve.Blazor.WebStorage; -using StateR.Interceptors; -using StateR.Internal; +//using StateR.Blazor.Persistance; using StateR.Updaters; -using System; -using System.Reflection; namespace CounterApp.Features; public class Counter { - [Persist] + //[Persist] public record class State(int Count) : StateBase; public class InitialState : IInitialState @@ -21,10 +15,10 @@ public class InitialState : IInitialState public State Value => new(0); } - public record class Increment : IAction; - public record class Decrement : IAction; - public record class SetPositive(int Count) : IAction; - public record class SetNegative(int Count) : IAction; + public record class Increment : IAction; + public record class Decrement : IAction; + public record class SetPositive(int Count) : IAction; + public record class SetNegative(int Count) : IAction; public class Updaters : IUpdater, IUpdater, IUpdater, IUpdater { @@ -38,28 +32,28 @@ public State Update(SetNegative action, State state) => state with { Count = action.Count }; } - public class SetPositiveValidator : AbstractValidator - { - public SetPositiveValidator() - { - RuleFor(x => x.Count).GreaterThan(0); - } - } + //public class SetPositiveValidator : AbstractValidator + //{ + // public SetPositiveValidator() + // { + // RuleFor(x => x.Count).GreaterThan(0); + // } + //} - public class SetNegativeValidator : AbstractValidator - { - public SetNegativeValidator() - { - RuleFor(x => x.Count).LessThan(0); - } - } + //public class SetNegativeValidator : AbstractValidator + //{ + // public SetNegativeValidator() + // { + // RuleFor(x => x.Count).LessThan(0); + // } + //} - public class StateValidator : AbstractValidator - { - public StateValidator() - { - RuleFor(x => x.Count).GreaterThan(-100); - RuleFor(x => x.Count).LessThan(100); - } - } + //public class StateValidator : AbstractValidator + //{ + // public StateValidator() + // { + // RuleFor(x => x.Count).GreaterThan(-100); + // RuleFor(x => x.Count).LessThan(100); + // } + //} } \ No newline at end of file diff --git a/samples/CounterApp/CounterApp/Features/WeatherForecast.cs b/samples/CounterApp/CounterApp/Features/WeatherForecast.cs index c1c294d..e0f1818 100644 --- a/samples/CounterApp/CounterApp/Features/WeatherForecast.cs +++ b/samples/CounterApp/CounterApp/Features/WeatherForecast.cs @@ -1,7 +1,5 @@ using StateR; -using StateR.AfterEffects; using StateR.AsyncLogic; -using StateR.Interceptors; using StateR.Internal; using StateR.Updaters; using System.Collections.Immutable; @@ -37,13 +35,13 @@ public State Update(Reload action, State state) => state with { Status = AsyncOperationStatus.Idle, Forecasts = ImmutableList.Create() }; } - public class ReloadEffect : IAfterEffects - { - public async Task HandleAfterEffectAsync(IDispatchContext context, CancellationToken cancellationToken) - { - await context.Dispatcher.DispatchAsync(new Fetch(), cancellationToken); - } - } + //public class ReloadEffect : IAfterEffects + //{ + // public async Task HandleAfterEffectAsync(IDispatchContext context, CancellationToken cancellationToken) + // { + // await context.Dispatcher.DispatchAsync(new Fetch(), cancellationToken); + // } + //} public class FetchOperation : AsyncOperation { @@ -61,12 +59,12 @@ protected override async Task LoadAsync(Fetch action, State initalState } } - public class Delays : IInterceptor> - { - public async Task InterceptAsync(IDispatchContext> context, CancellationToken cancellationToken) - { - Console.WriteLine($"{context.Action.GetType().GetStatorName()}: {context.Action.status}"); - await Task.Delay(2000, cancellationToken); - } - } + //public class Delays : IInterceptor> + //{ + // public async Task InterceptAsync(IDispatchContext> context, CancellationToken cancellationToken) + // { + // Console.WriteLine($"{context.Action.GetType().GetStatorName()}: {context.Action.status}"); + // await Task.Delay(2000, cancellationToken); + // } + //} } diff --git a/samples/CounterApp/CounterApp/Pages/FetchData.razor b/samples/CounterApp/CounterApp/Pages/FetchData.razor index bfb84d9..f66ae6b 100644 --- a/samples/CounterApp/CounterApp/Pages/FetchData.razor +++ b/samples/CounterApp/CounterApp/Pages/FetchData.razor @@ -1,4 +1,4 @@ -@using StateR.AsyncLogic +@*@using StateR.AsyncLogic @using StateR.Blazor.Components @page "/fetchdata" @inherits StatorComponent @@ -10,14 +10,14 @@

This component demonstrates fetching data from the server.

Async Status: @WeatherState.Current.Status

- +*@ @**@ @**@ - +@*
Loading... @@ -60,3 +60,4 @@
+*@ \ No newline at end of file diff --git a/samples/CounterApp/CounterApp/Program.cs b/samples/CounterApp/CounterApp/Program.cs index 3065212..36530f0 100644 --- a/samples/CounterApp/CounterApp/Program.cs +++ b/samples/CounterApp/CounterApp/Program.cs @@ -1,15 +1,12 @@ using CounterApp; -using CounterApp.Features; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using StateR; -using StateR.AfterEffects; -using StateR.Blazor.Persistance; -using StateR.Blazor.ReduxDevTools; +//using StateR.Blazor.Persistance; +//using StateR.Blazor.ReduxDevTools; using ForEvolve.Blazor.WebStorage; -using StateR.Experiments.AsyncLogic; -using StateR.Interceptors; -using StateR.Validations.FluentValidation; +//using StateR.Experiments.AsyncLogic; +//using StateR.Validations.FluentValidation; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); @@ -43,13 +40,13 @@ public static void RegisterServices(this IServiceCollection services) var appAssembly = typeof(App).Assembly; services .AddStateR(appAssembly) - .AddAsyncOperations() - .AddReduxDevTools() - .AddFluentValidation(appAssembly) - .Apply(buidler => buidler + //.AddAsyncOperations() + //.AddReduxDevTools() + //.AddFluentValidation(appAssembly) + .Apply(/*buidler => buidler .AddPersistence() .AddStateValidation() - ) + */) ; services.AddSingleton(sp => new HttpClient { BaseAddress = new Uri(sp.GetRequiredService().BaseAddress) }); } diff --git a/samples/CounterApp/CounterApp/Shared/FluentValidationSummary.razor b/samples/CounterApp/CounterApp/Shared/FluentValidationSummary.razor index 3c74c88..3bd1c75 100644 --- a/samples/CounterApp/CounterApp/Shared/FluentValidationSummary.razor +++ b/samples/CounterApp/CounterApp/Shared/FluentValidationSummary.razor @@ -1,4 +1,4 @@ -@using FluentValidation.Results +@*@using FluentValidation.Results @using StateR.Validations.FluentValidation; @inherits StatorComponent @inject IState ValidationState @@ -29,3 +29,4 @@ await DispatchAsync(new CleanValidationError()); } } +*@ \ No newline at end of file diff --git a/samples/CounterApp/CounterApp/Shared/MainLayout.razor b/samples/CounterApp/CounterApp/Shared/MainLayout.razor index 8751a37..73eba5d 100644 --- a/samples/CounterApp/CounterApp/Shared/MainLayout.razor +++ b/samples/CounterApp/CounterApp/Shared/MainLayout.razor @@ -1,4 +1,4 @@ -@using StateR.Blazor.Components; +@*@using StateR.Blazor.Components;*@ @inherits LayoutComponentBase
@@ -15,5 +15,5 @@ @Body - + @**@
diff --git a/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsInterop.cs b/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsInterop.cs index cf9133d..3e98ce1 100644 --- a/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsInterop.cs +++ b/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsInterop.cs @@ -1,7 +1,6 @@ using Microsoft.JSInterop; using StateR.Internal; using StateR.Updaters; -using StateR.Updaters.Hooks; using System.Collections; using System.Reflection; using System.Text.Json; diff --git a/src/StateR.Blazor/StatorComponentBase.cs b/src/StateR.Blazor/StatorComponentBase.cs index b0bcaa4..f90d387 100644 --- a/src/StateR.Blazor/StatorComponentBase.cs +++ b/src/StateR.Blazor/StatorComponentBase.cs @@ -10,13 +10,21 @@ public abstract class StatorComponentBase : ComponentBase, IDisposable [Inject] public IDispatcher? Dispatcher { get; set; } - protected virtual async Task DispatchAsync(TAction action, CancellationToken cancellationToken = default) - where TAction : IAction + protected virtual async Task DispatchAsync(object action, CancellationToken cancellationToken = default) { GuardAgainstNullDispatcher(); await Dispatcher.DispatchAsync(action, cancellationToken); } + + protected virtual async Task DispatchAsync(TAction action, CancellationToken cancellationToken = default) + where TAction : IAction + where TState : StateBase + { + GuardAgainstNullDispatcher(); + await Dispatcher.DispatchAsync(action, cancellationToken); + } + private void Dispose(bool disposing) { if (!_disposedValue) diff --git a/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs b/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs index e7fbd92..9496dda 100644 --- a/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs +++ b/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs @@ -13,7 +13,7 @@ public static IStatorBuilder AddAsyncOperations(this IStatorBuilder builder) builder.AddTypes(new[] { typeof(StatusUpdated<>) }); // Async Operation's Errors - builder.Services.TryAddSingleton, UpdaterActionHandler>(); + builder.Services.TryAddSingleton, UpdaterMiddleware>(); builder.Services.TryAddSingleton, AsyncError.Updaters>(); builder.Services.TryAddSingleton, AsyncError.InitialState>(); builder.Services.TryAddSingleton, Internal.State>(); diff --git a/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs b/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs index 963b5e7..d01fa92 100644 --- a/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs +++ b/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs @@ -1,7 +1,7 @@ using FluentValidation; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using StateR.Interceptors; +using StateR.Pipeline; using System.Reflection; namespace StateR.Validations.FluentValidation; @@ -23,7 +23,7 @@ public static IStatorBuilder AddFluentValidation(this IStatorBuilder builder, pa }); // Validation interceptor and state - builder.Services.TryAddSingleton(typeof(IInterceptor<>), typeof(ValidationInterceptor<>)); + builder.Services.TryAddSingleton(typeof(IActionFilter<,>), typeof(ValidationInterceptor<>)); // Scan for validators builder.Services.AddValidatorsFromAssemblies(assembliesToScan, ServiceLifetime.Singleton); diff --git a/src/StateR/ActionHandlers/ActionHandlersManager.cs b/src/StateR/ActionHandlers/ActionHandlersManager.cs deleted file mode 100644 index df9c673..0000000 --- a/src/StateR/ActionHandlers/ActionHandlersManager.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using StateR.ActionHandlers.Hooks; - -namespace StateR.ActionHandlers; - -public class ActionHandlersManager : IActionHandlersManager -{ - private readonly IActionHandlerHooksCollection _hooksCollection; - private readonly IServiceProvider _serviceProvider; - - public ActionHandlersManager(IActionHandlerHooksCollection hooksCollection, IServiceProvider serviceProvider) - { - _hooksCollection = hooksCollection ?? throw new ArgumentNullException(nameof(hooksCollection)); - _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - } - - public async Task DispatchAsync(IDispatchContext dispatchContext) where TAction : IAction - { - var updaterHandlers = _serviceProvider.GetServices>().ToList(); - foreach (var handler in updaterHandlers) - { - dispatchContext.CancellationToken.ThrowIfCancellationRequested(); - - await _hooksCollection.BeforeHandlerAsync(dispatchContext, handler, dispatchContext.CancellationToken); - await handler.HandleAsync(dispatchContext, dispatchContext.CancellationToken); - await _hooksCollection.AfterHandlerAsync(dispatchContext, handler, dispatchContext.CancellationToken); - } - } -} diff --git a/src/StateR/ActionHandlers/Hooks/ActionHandlerHooksCollection.cs b/src/StateR/ActionHandlers/Hooks/ActionHandlerHooksCollection.cs deleted file mode 100644 index f2d8eab..0000000 --- a/src/StateR/ActionHandlers/Hooks/ActionHandlerHooksCollection.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace StateR.ActionHandlers.Hooks; - -public class ActionHandlerHooksCollection : IActionHandlerHooksCollection -{ - private readonly IEnumerable _beforeActionHooks; - private readonly IEnumerable _afterActionHooks; - public ActionHandlerHooksCollection(IEnumerable beforeActionHooks, IEnumerable afterActionHooks) - { - _beforeActionHooks = beforeActionHooks ?? throw new ArgumentNullException(nameof(beforeActionHooks)); - _afterActionHooks = afterActionHooks ?? throw new ArgumentNullException(nameof(afterActionHooks)); - } - - public async Task BeforeHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction - { - foreach (var hook in _beforeActionHooks) - { - await hook.BeforeHandlerAsync(context, actionHandler, cancellationToken); - } - } - - public async Task AfterHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction - { - foreach (var hook in _afterActionHooks) - { - await hook.AfterHandlerAsync(context, actionHandler, cancellationToken); - } - } -} diff --git a/src/StateR/ActionHandlers/Hooks/IActionHandlerHooksCollection.cs b/src/StateR/ActionHandlers/Hooks/IActionHandlerHooksCollection.cs deleted file mode 100644 index 2964422..0000000 --- a/src/StateR/ActionHandlers/Hooks/IActionHandlerHooksCollection.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace StateR.ActionHandlers.Hooks; - -public interface IActionHandlerHooksCollection -{ - Task BeforeHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction; - Task AfterHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction; -} diff --git a/src/StateR/ActionHandlers/Hooks/IAfterActionHook.cs b/src/StateR/ActionHandlers/Hooks/IAfterActionHook.cs deleted file mode 100644 index b1ecd82..0000000 --- a/src/StateR/ActionHandlers/Hooks/IAfterActionHook.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace StateR.ActionHandlers.Hooks; - -public interface IAfterActionHook -{ - Task AfterHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction; -} diff --git a/src/StateR/ActionHandlers/Hooks/IBeforeActionHook.cs b/src/StateR/ActionHandlers/Hooks/IBeforeActionHook.cs deleted file mode 100644 index c69f7c8..0000000 --- a/src/StateR/ActionHandlers/Hooks/IBeforeActionHook.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace StateR.ActionHandlers.Hooks; - -public interface IBeforeActionHook -{ - Task BeforeHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction; -} diff --git a/src/StateR/ActionHandlers/IActionHandler.cs b/src/StateR/ActionHandlers/IActionHandler.cs deleted file mode 100644 index 4f7747f..0000000 --- a/src/StateR/ActionHandlers/IActionHandler.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace StateR.ActionHandlers; - -public interface IActionHandler - where TAction : IAction -{ - Task HandleAsync(IDispatchContext context, CancellationToken cancellationToken); -} diff --git a/src/StateR/ActionHandlers/IActionHandlersManager.cs b/src/StateR/ActionHandlers/IActionHandlersManager.cs deleted file mode 100644 index aa0a671..0000000 --- a/src/StateR/ActionHandlers/IActionHandlersManager.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace StateR.ActionHandlers; - -public interface IActionHandlersManager : IDispatchManager { } diff --git a/src/StateR/AfterEffects/AfterEffectsManager.cs b/src/StateR/AfterEffects/AfterEffectsManager.cs deleted file mode 100644 index 72cdb58..0000000 --- a/src/StateR/AfterEffects/AfterEffectsManager.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using StateR.AfterEffects.Hooks; - -namespace StateR.AfterEffects; - -public class AfterEffectsManager : IAfterEffectsManager -{ - private readonly IAfterEffectHooksCollection _hooks; - private readonly IServiceProvider _serviceProvider; - - public AfterEffectsManager(IAfterEffectHooksCollection hooks, IServiceProvider serviceProvider) - { - _hooks = hooks ?? throw new ArgumentNullException(nameof(hooks)); - _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - } - - public async Task DispatchAsync(IDispatchContext dispatchContext) where TAction : IAction - { - var afterEffects = _serviceProvider.GetServices>().ToList(); - foreach (var afterEffect in afterEffects) - { - dispatchContext.CancellationToken.ThrowIfCancellationRequested(); - - await _hooks.BeforeHandlerAsync(dispatchContext, afterEffect, dispatchContext.CancellationToken); - await afterEffect.HandleAfterEffectAsync(dispatchContext, dispatchContext.CancellationToken); - await _hooks.AfterHandlerAsync(dispatchContext, afterEffect, dispatchContext.CancellationToken); - } - } -} diff --git a/src/StateR/AfterEffects/Hooks/AfterEffectHooksCollection.cs b/src/StateR/AfterEffects/Hooks/AfterEffectHooksCollection.cs deleted file mode 100644 index 1e6a603..0000000 --- a/src/StateR/AfterEffects/Hooks/AfterEffectHooksCollection.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace StateR.AfterEffects.Hooks; - -public class AfterEffectHooksCollection : IAfterEffectHooksCollection -{ - private readonly IEnumerable _beforeAfterEffectHooks; - private readonly IEnumerable _afterAfterEffectHooks; - public AfterEffectHooksCollection(IEnumerable beforeAfterEffectHooks, IEnumerable afterAfterEffectHooks) - { - _beforeAfterEffectHooks = beforeAfterEffectHooks ?? throw new ArgumentNullException(nameof(beforeAfterEffectHooks)); - _afterAfterEffectHooks = afterAfterEffectHooks ?? throw new ArgumentNullException(nameof(afterAfterEffectHooks)); - } - - public async Task BeforeHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction - { - foreach (var hook in _beforeAfterEffectHooks) - { - await hook.BeforeHandlerAsync(context, afterEffect, cancellationToken); - } - } - - public async Task AfterHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction - { - foreach (var hook in _afterAfterEffectHooks) - { - await hook.AfterHandlerAsync(context, afterEffect, cancellationToken); - } - } -} - diff --git a/src/StateR/AfterEffects/Hooks/IAfterAfterEffectHook.cs b/src/StateR/AfterEffects/Hooks/IAfterAfterEffectHook.cs deleted file mode 100644 index 8b8d888..0000000 --- a/src/StateR/AfterEffects/Hooks/IAfterAfterEffectHook.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace StateR.AfterEffects.Hooks; - -public interface IAfterAfterEffectHook -{ - Task AfterHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction; -} - diff --git a/src/StateR/AfterEffects/Hooks/IAfterEffectHooksCollection.cs b/src/StateR/AfterEffects/Hooks/IAfterEffectHooksCollection.cs deleted file mode 100644 index d0e4ce9..0000000 --- a/src/StateR/AfterEffects/Hooks/IAfterEffectHooksCollection.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace StateR.AfterEffects.Hooks; - -public interface IAfterEffectHooksCollection -{ - Task BeforeHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction; - Task AfterHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction; -} - diff --git a/src/StateR/AfterEffects/Hooks/IBeforeAfterEffectHook.cs b/src/StateR/AfterEffects/Hooks/IBeforeAfterEffectHook.cs deleted file mode 100644 index 7a473b7..0000000 --- a/src/StateR/AfterEffects/Hooks/IBeforeAfterEffectHook.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace StateR.AfterEffects.Hooks; - -public interface IBeforeAfterEffectHook -{ - Task BeforeHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction; -} - diff --git a/src/StateR/AfterEffects/IAfterEffects.cs b/src/StateR/AfterEffects/IAfterEffects.cs deleted file mode 100644 index b8761a2..0000000 --- a/src/StateR/AfterEffects/IAfterEffects.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace StateR.AfterEffects; - -public interface IAfterEffects - where TAction : IAction -{ - Task HandleAfterEffectAsync(IDispatchContext context, CancellationToken cancellationToken); -} diff --git a/src/StateR/AfterEffects/IAfterEffectsManager.cs b/src/StateR/AfterEffects/IAfterEffectsManager.cs deleted file mode 100644 index 20aa657..0000000 --- a/src/StateR/AfterEffects/IAfterEffectsManager.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace StateR.AfterEffects; - -public interface IAfterEffectsManager : IDispatchManager { } diff --git a/src/StateR/DispatchContext.cs b/src/StateR/DispatchContext.cs index 8f7ea55..085ca0b 100644 --- a/src/StateR/DispatchContext.cs +++ b/src/StateR/DispatchContext.cs @@ -1,7 +1,8 @@ namespace StateR; -public class DispatchContext : IDispatchContext - where TAction : IAction +public class DispatchContext : IDispatchContext + where TAction : IAction + where TState : StateBase { private readonly CancellationTokenSource _cancellationTokenSource; public DispatchContext(TAction action, IDispatcher dispatcher, CancellationTokenSource cancellationTokenSource) @@ -16,12 +17,11 @@ public DispatchContext(TAction action, IDispatcher dispatcher, CancellationToken public CancellationToken CancellationToken => _cancellationTokenSource.Token; public void Cancel() - => throw new DispatchCancelledException(Action); - //=> _cancellationTokenSource.Cancel(true); + => throw new DispatchCancelledException(Action.GetType()); } public class DispatchCancelledException : Exception { - public DispatchCancelledException(IAction action) - : base($"The dispatch operation '{action.GetType().FullName}' has been cancelled.") { } + public DispatchCancelledException(Type actionType) + : base($"The dispatch operation '{actionType.FullName}' has been cancelled.") { } } \ No newline at end of file diff --git a/src/StateR/DispatchContextFactory.cs b/src/StateR/DispatchContextFactory.cs index 4238ffc..9ba0810 100644 --- a/src/StateR/DispatchContextFactory.cs +++ b/src/StateR/DispatchContextFactory.cs @@ -2,7 +2,8 @@ public class DispatchContextFactory : IDispatchContextFactory { - public IDispatchContext Create(TAction action, IDispatcher dispatcher, CancellationTokenSource cancellationTokenSource) - where TAction : IAction - => new DispatchContext(action, dispatcher, cancellationTokenSource); + public IDispatchContext Create(TAction action, IDispatcher dispatcher, CancellationTokenSource cancellationTokenSource) + where TAction : IAction + where TState : StateBase + => new DispatchContext(action, dispatcher, cancellationTokenSource); } diff --git a/src/StateR/Dispatcher.cs b/src/StateR/Dispatcher.cs index 607c2fb..63eff9d 100644 --- a/src/StateR/Dispatcher.cs +++ b/src/StateR/Dispatcher.cs @@ -1,44 +1,58 @@ using Microsoft.Extensions.Logging; -using StateR.ActionHandlers; -using StateR.AfterEffects; -using StateR.Interceptors; +using StateR.Pipeline; using System; namespace StateR; public class Dispatcher : IDispatcher { - private readonly IInterceptorsManager _interceptorsManager; - private readonly IActionHandlersManager _actionHandlersManager; - private readonly IAfterEffectsManager _afterEffectsManager; private readonly IDispatchContextFactory _dispatchContextFactory; + private readonly IActionFilterFactory _actionFilterFactory; private readonly ILogger _logger; - public Dispatcher(IDispatchContextFactory dispatchContextFactory, IInterceptorsManager interceptorsManager, IActionHandlersManager actionHandlersManager, IAfterEffectsManager afterEffectsManager, ILogger logger) + public Dispatcher(IDispatchContextFactory dispatchContextFactory, IActionFilterFactory actionFilterFactory, ILogger logger) { _dispatchContextFactory = dispatchContextFactory ?? throw new ArgumentNullException(nameof(dispatchContextFactory)); - _interceptorsManager = interceptorsManager ?? throw new ArgumentNullException(nameof(interceptorsManager)); - _actionHandlersManager = actionHandlersManager ?? throw new ArgumentNullException(nameof(actionHandlersManager)); - _afterEffectsManager = afterEffectsManager ?? throw new ArgumentNullException(nameof(afterEffectsManager)); + _actionFilterFactory = actionFilterFactory ?? throw new ArgumentNullException(nameof(actionFilterFactory)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - public async Task DispatchAsync(TAction action, CancellationToken cancellationToken) where TAction : IAction + public async Task DispatchAsync(TAction action, CancellationToken cancellationToken) + where TAction : IAction + where TState : StateBase { using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - var dispatchContext = _dispatchContextFactory.Create(action, this, cancellationTokenSource); - // - // TODO: design how to handle OperationCanceledException - // + var dispatchContext = _dispatchContextFactory.Create(action, this, cancellationTokenSource); + var actionFilter = _actionFilterFactory.Create(dispatchContext); try { - await _interceptorsManager.DispatchAsync(dispatchContext); - await _actionHandlersManager.DispatchAsync(dispatchContext); - await _afterEffectsManager.DispatchAsync(dispatchContext); + await actionFilter.InvokeAsync(dispatchContext, null, cancellationToken); } catch (DispatchCancelledException ex) { _logger.LogWarning(ex, ex.Message); } } + + public Task DispatchAsync(object action, CancellationToken cancellationToken) + { + var actionType = action + .GetType(); + var actionInterface = actionType.GetInterfaces() + .FirstOrDefault(@interface => @interface.IsGenericType && @interface.GetGenericTypeDefinition() == typeof(IAction<>)); + if (actionInterface == null) + { + // TODO: Find a better exception + throw new InvalidOperationException($"The action must implement the {typeof(IAction<>).Name} interface."); + } + var stateType = actionInterface.GetGenericArguments()[0]; + var method = GetType().GetMethods().FirstOrDefault(m => m.IsGenericMethod && m.Name == nameof(DispatchAsync)); + if(method == null) + { + throw new MissingMethodException(nameof(Dispatcher), nameof(DispatchAsync)); + } + var genericMethod = method.MakeGenericMethod(actionType, stateType); + var task = genericMethod.Invoke(this, new[] { action, cancellationToken }); + return (Task)task!; + } } diff --git a/src/StateR/IAction.cs b/src/StateR/IAction.cs index 648e6b5..c4138a9 100644 --- a/src/StateR/IAction.cs +++ b/src/StateR/IAction.cs @@ -1,3 +1,3 @@ namespace StateR; -public interface IAction { } +public interface IAction where TState : StateBase { } diff --git a/src/StateR/IDispatchContext.cs b/src/StateR/IDispatchContext.cs index 0f96e31..bfe5295 100644 --- a/src/StateR/IDispatchContext.cs +++ b/src/StateR/IDispatchContext.cs @@ -1,7 +1,8 @@ namespace StateR; -public interface IDispatchContext - where TAction : IAction +public interface IDispatchContext + where TAction : IAction + where TState : StateBase { IDispatcher Dispatcher { get; } TAction Action { get; } diff --git a/src/StateR/IDispatchContextFactory.cs b/src/StateR/IDispatchContextFactory.cs index 6613960..1abb378 100644 --- a/src/StateR/IDispatchContextFactory.cs +++ b/src/StateR/IDispatchContextFactory.cs @@ -2,5 +2,7 @@ public interface IDispatchContextFactory { - IDispatchContext Create(TAction action, IDispatcher dispatcher, CancellationTokenSource cancellationTokenSource) where TAction : IAction; + IDispatchContext Create(TAction action, IDispatcher dispatcher, CancellationTokenSource cancellationTokenSource) + where TAction : IAction + where TState : StateBase; } diff --git a/src/StateR/IDispatchManager.cs b/src/StateR/IDispatchManager.cs index 3cdc889..70b1c50 100644 --- a/src/StateR/IDispatchManager.cs +++ b/src/StateR/IDispatchManager.cs @@ -2,6 +2,7 @@ public interface IDispatchManager { - Task DispatchAsync(IDispatchContext dispatchContext) - where TAction : IAction; + Task DispatchAsync(IDispatchContext dispatchContext) + where TAction : IAction + where TState : StateBase; } diff --git a/src/StateR/IDispatcher.cs b/src/StateR/IDispatcher.cs index 6ef0bf0..510ccf1 100644 --- a/src/StateR/IDispatcher.cs +++ b/src/StateR/IDispatcher.cs @@ -2,5 +2,8 @@ public interface IDispatcher { - Task DispatchAsync(TAction action, CancellationToken cancellationToken) where TAction : IAction; + Task DispatchAsync(object action, CancellationToken cancellationToken); + Task DispatchAsync(TAction action, CancellationToken cancellationToken) + where TAction : IAction + where TState : StateBase; } diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index 2a2bd3d..f7605af 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -18,4 +18,8 @@ public interface IStatorBuilder IStatorBuilder AddActions(IEnumerable states); IStatorBuilder AddUpdaters(IEnumerable states); IStatorBuilder AddActionHandlers(IEnumerable types); + + IStatorBuilder AddMiddlewares(IEnumerable types); + List Middlewares { get; } + } diff --git a/src/StateR/Interceptors/Hooks/IAfterInterceptorHook.cs b/src/StateR/Interceptors/Hooks/IAfterInterceptorHook.cs deleted file mode 100644 index d57c3b8..0000000 --- a/src/StateR/Interceptors/Hooks/IAfterInterceptorHook.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace StateR.Interceptors.Hooks; - -public interface IAfterInterceptorHook -{ - Task AfterHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction; -} diff --git a/src/StateR/Interceptors/Hooks/IBeforeInterceptorHook.cs b/src/StateR/Interceptors/Hooks/IBeforeInterceptorHook.cs deleted file mode 100644 index 218ab38..0000000 --- a/src/StateR/Interceptors/Hooks/IBeforeInterceptorHook.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace StateR.Interceptors.Hooks; - -public interface IBeforeInterceptorHook -{ - Task BeforeHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction; -} diff --git a/src/StateR/Interceptors/Hooks/IInterceptorsHooksCollection.cs b/src/StateR/Interceptors/Hooks/IInterceptorsHooksCollection.cs deleted file mode 100644 index fa4b8f3..0000000 --- a/src/StateR/Interceptors/Hooks/IInterceptorsHooksCollection.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace StateR.Interceptors.Hooks; - -public interface IInterceptorsHooksCollection -{ - Task BeforeHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction; - Task AfterHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction; -} diff --git a/src/StateR/Interceptors/Hooks/InterceptorsHooksCollection.cs b/src/StateR/Interceptors/Hooks/InterceptorsHooksCollection.cs deleted file mode 100644 index ed254bc..0000000 --- a/src/StateR/Interceptors/Hooks/InterceptorsHooksCollection.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace StateR.Interceptors.Hooks; - -public class InterceptorsHooksCollection : IInterceptorsHooksCollection -{ - private readonly IEnumerable _beforeInterceptorHooks; - private readonly IEnumerable _afterInterceptorHooks; - public InterceptorsHooksCollection(IEnumerable beforeInterceptorHooks, IEnumerable afterInterceptorHooks) - { - _beforeInterceptorHooks = beforeInterceptorHooks ?? throw new ArgumentNullException(nameof(beforeInterceptorHooks)); - _afterInterceptorHooks = afterInterceptorHooks ?? throw new ArgumentNullException(nameof(afterInterceptorHooks)); - } - - public async Task BeforeHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction - { - foreach (var hook in _beforeInterceptorHooks) - { - await hook.BeforeHandlerAsync(context, interceptor, cancellationToken); - } - } - - public async Task AfterHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction - { - foreach (var hook in _afterInterceptorHooks) - { - await hook.AfterHandlerAsync(context, interceptor, cancellationToken); - } - } -} diff --git a/src/StateR/Interceptors/IInterceptor.cs b/src/StateR/Interceptors/IInterceptor.cs deleted file mode 100644 index 0ca7b9d..0000000 --- a/src/StateR/Interceptors/IInterceptor.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace StateR.Interceptors; - -public interface IInterceptor - where TAction : IAction -{ - Task InterceptAsync(IDispatchContext context, CancellationToken cancellationToken); -} diff --git a/src/StateR/Interceptors/IInterceptorsManager.cs b/src/StateR/Interceptors/IInterceptorsManager.cs deleted file mode 100644 index 23f6853..0000000 --- a/src/StateR/Interceptors/IInterceptorsManager.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace StateR.Interceptors; - -public interface IInterceptorsManager : IDispatchManager { } diff --git a/src/StateR/Interceptors/InterceptorsManager.cs b/src/StateR/Interceptors/InterceptorsManager.cs deleted file mode 100644 index d23c6ea..0000000 --- a/src/StateR/Interceptors/InterceptorsManager.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using StateR.Interceptors.Hooks; - -namespace StateR.Interceptors; - -public class InterceptorsManager : IInterceptorsManager -{ - private readonly IInterceptorsHooksCollection _hooks; - private readonly IServiceProvider _serviceProvider; - - public InterceptorsManager(IInterceptorsHooksCollection hooks, IServiceProvider serviceProvider) - { - _hooks = hooks ?? throw new ArgumentNullException(nameof(hooks)); - _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - } - - public async Task DispatchAsync(IDispatchContext dispatchContext) where TAction : IAction - { - var interceptors = _serviceProvider.GetServices>().ToList(); - foreach (var interceptor in interceptors) - { - dispatchContext.CancellationToken.ThrowIfCancellationRequested(); - - await _hooks.BeforeHandlerAsync(dispatchContext, interceptor, dispatchContext.CancellationToken); - await interceptor.InterceptAsync(dispatchContext, dispatchContext.CancellationToken); - await _hooks.AfterHandlerAsync(dispatchContext, interceptor, dispatchContext.CancellationToken); - } - } -} diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index d83df9e..9148111 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -29,6 +29,10 @@ public IStatorBuilder AddActionHandlers(IEnumerable types) public List Updaters { get; } = new List(); public List All { get; } = new List(); + public IStatorBuilder AddMiddlewares(IEnumerable types) + => AddDistinctTypes(Middlewares, types); + public List Middlewares { get; } = new List(); + private IStatorBuilder AddDistinctTypes(List list, IEnumerable types) { var distinctTypes = types.Except(list).Distinct(); diff --git a/src/StateR/Internal/TypeScannerBuilderExtensions.cs b/src/StateR/Internal/TypeScannerBuilderExtensions.cs index aef5c0c..628dbd0 100644 --- a/src/StateR/Internal/TypeScannerBuilderExtensions.cs +++ b/src/StateR/Internal/TypeScannerBuilderExtensions.cs @@ -1,4 +1,4 @@ -using StateR.ActionHandlers; +using StateR.Pipeline; using StateR.Updaters; using System.Reflection; @@ -17,7 +17,7 @@ public static IStatorBuilder ScanTypes(this IStatorBuilder builder) var updaters = TypeScanner.FindUpdaters(builder.All); builder.AddUpdaters(updaters); - var actionHandlers = TypeScanner.FindActionHandlers(builder.All); + var actionHandlers = TypeScanner.FindMiddlewares(builder.All); builder.AddUpdaters(actionHandlers); return builder; @@ -38,7 +38,7 @@ public static IEnumerable FindActions(IEnumerable types) .Where(type => !type.IsAbstract && type .GetTypeInfo() .GetInterfaces() - .Any(i => i == typeof(IAction)) + .Any(i => i == typeof(IAction<>)) ); return actions; } @@ -53,34 +53,14 @@ public static IEnumerable FindUpdaters(IEnumerable types) ); return updaters; } - public static IEnumerable FindActionHandlers(IEnumerable types) + public static IEnumerable FindMiddlewares(IEnumerable types) { var handlers = types .Where(type => !type.IsAbstract && type .GetTypeInfo() .GetInterfaces() - .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IActionHandler<>)) + .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IActionFilter<,>)) ); return handlers; } - - //public IStatorBuilder FindInterceptors(this IStatorBuilder builder) - //{ - // var iActionInterceptor = typeof(IInterceptor<>); - // return types.Where(type => !type.IsAbstract && type - // .GetTypeInfo() - // .GetInterfaces() - // .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == iActionInterceptor) - // ); - //} - - //public IStatorBuilder FindAfterEffects(this IStatorBuilder builder) - //{ - // var iAfterEffects = typeof(IAfterEffects<>); - // return types.Where(type => !type.IsAbstract && type - // .GetTypeInfo() - // .GetInterfaces() - // .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == iAfterEffects) - // ); - //} } \ No newline at end of file diff --git a/src/StateR/Pipeline/IActionFilter.cs b/src/StateR/Pipeline/IActionFilter.cs new file mode 100644 index 0000000..2b97cc9 --- /dev/null +++ b/src/StateR/Pipeline/IActionFilter.cs @@ -0,0 +1,13 @@ + +namespace StateR.Pipeline; + +public interface IActionFilter + where TAction : IAction + where TState : StateBase +{ + Task InvokeAsync(IDispatchContext context, ActionDelegate? next, CancellationToken cancellationToken); +} + +public delegate Task ActionDelegate(IDispatchContext context, CancellationToken cancellationToken) + where TAction : IAction + where TState : StateBase; diff --git a/src/StateR/Pipeline/IActionFilterFactory.cs b/src/StateR/Pipeline/IActionFilterFactory.cs new file mode 100644 index 0000000..10cb79a --- /dev/null +++ b/src/StateR/Pipeline/IActionFilterFactory.cs @@ -0,0 +1,26 @@ + +using Microsoft.Extensions.DependencyInjection; +using System; +namespace StateR.Pipeline; + +public interface IActionFilterFactory +{ + IActionFilter Create(IDispatchContext context) + where TAction : IAction + where TState : StateBase; +} + +public class ActionFilterFactory : IActionFilterFactory +{ + private readonly IServiceProvider _serviceProvider; + public ActionFilterFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + } + + public IActionFilter Create(IDispatchContext context) + where TAction : IAction + where TState : StateBase + => _serviceProvider.GetRequiredService>(); + +} \ No newline at end of file diff --git a/src/StateR/StatorStartupExtensions.cs b/src/StateR/StatorStartupExtensions.cs index 093f7bd..5c64773 100644 --- a/src/StateR/StatorStartupExtensions.cs +++ b/src/StateR/StatorStartupExtensions.cs @@ -1,14 +1,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using StateR.ActionHandlers; -using StateR.ActionHandlers.Hooks; -using StateR.AfterEffects; -using StateR.AfterEffects.Hooks; -using StateR.Interceptors; -using StateR.Interceptors.Hooks; using StateR.Internal; +using StateR.Pipeline; using StateR.Updaters; -using StateR.Updaters.Hooks; using System.Reflection; namespace StateR; @@ -19,16 +13,17 @@ public static IStatorBuilder AddStateR(this IServiceCollection services) { services.TryAddSingleton(); services.TryAddSingleton(); - - services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); + + //services.TryAddSingleton(); + //services.TryAddSingleton(); + //services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); + //services.TryAddSingleton(); + //services.TryAddSingleton(); + //services.TryAddSingleton(); + //services.TryAddSingleton(); return new StatorBuilder(services); } @@ -41,6 +36,11 @@ public static IStatorBuilder AddStateR(this IServiceCollection services, params return builder.AddTypes(allTypes); } + //public static IStatorBuilder AddMiddleware(this IStatorBuilder builder) + //{ + + //} + public static IServiceCollection Apply(this IStatorBuilder builder, Action? postConfiguration = null) { // Extract types @@ -55,51 +55,51 @@ public static IServiceCollection Apply(this IStatorBuilder builder, Action(); - .AddClasses(classes => classes.AssignableTo(typeof(IBeforeInterceptorHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IAfterInterceptorHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IBeforeAfterEffectHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IAfterAfterEffectHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IBeforeActionHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IAfterActionHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IBeforeUpdateHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IAfterUpdateHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - - // Equivalent to: AddSingleton, Implementation>(); - .AddClasses(classes => classes.AssignableTo(typeof(IInterceptor<>))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - - // Equivalent to: AddSingleton, Implementation>(); - .AddClasses(classes => classes.AssignableTo(typeof(IAfterEffects<>))) - .AsImplementedInterfaces() - .WithSingletonLifetime() + //// Equivalent to: AddSingleton(); + //.AddClasses(classes => classes.AssignableTo(typeof(IBeforeInterceptorHook))) + //.AsImplementedInterfaces() + //.WithSingletonLifetime() + //// Equivalent to: AddSingleton(); + //.AddClasses(classes => classes.AssignableTo(typeof(IAfterInterceptorHook))) + //.AsImplementedInterfaces() + //.WithSingletonLifetime() + + //// Equivalent to: AddSingleton(); + //.AddClasses(classes => classes.AssignableTo(typeof(IBeforeAfterEffectHook))) + //.AsImplementedInterfaces() + //.WithSingletonLifetime() + //// Equivalent to: AddSingleton(); + //.AddClasses(classes => classes.AssignableTo(typeof(IAfterAfterEffectHook))) + //.AsImplementedInterfaces() + //.WithSingletonLifetime() + + //// Equivalent to: AddSingleton(); + //.AddClasses(classes => classes.AssignableTo(typeof(IBeforeActionHook))) + //.AsImplementedInterfaces() + //.WithSingletonLifetime() + //// Equivalent to: AddSingleton(); + //.AddClasses(classes => classes.AssignableTo(typeof(IAfterActionHook))) + //.AsImplementedInterfaces() + //.WithSingletonLifetime() + + //// Equivalent to: AddSingleton(); + //.AddClasses(classes => classes.AssignableTo(typeof(IBeforeUpdateHook))) + //.AsImplementedInterfaces() + //.WithSingletonLifetime() + //// Equivalent to: AddSingleton(); + //.AddClasses(classes => classes.AssignableTo(typeof(IAfterUpdateHook))) + //.AsImplementedInterfaces() + //.WithSingletonLifetime() + + //// Equivalent to: AddSingleton, Implementation>(); + //.AddClasses(classes => classes.AssignableTo(typeof(IInterceptor<>))) + //.AsImplementedInterfaces() + //.WithSingletonLifetime() + + //// Equivalent to: AddSingleton, Implementation>(); + //.AddClasses(classes => classes.AssignableTo(typeof(IAfterEffects<>))) + //.AsImplementedInterfaces() + //.WithSingletonLifetime() ); // Register States @@ -113,10 +113,10 @@ public static IServiceCollection Apply(this IStatorBuilder builder, Action); - var updaterHandler = typeof(UpdaterActionHandler<,>); - var handlerType = typeof(IActionHandler<>); + var updaterHandler = typeof(UpdaterMiddleware<,>); + var handlerType = typeof(IActionFilter<,>); foreach (var updater in builder.Updaters) { Console.WriteLine($"updater: {updater.FullName}"); @@ -124,21 +124,27 @@ public static IServiceCollection Apply(this IStatorBuilder builder, Action i.IsGenericType && i.GetGenericTypeDefinition() == iUpdaterType); foreach (var @interface in interfaces) { - // Equivalent to: AddSingleton, UpdaterHandler> + // Equivalent to: AddSingleton, UpdaterMiddleware> var actionType = @interface.GenericTypeArguments[0]; var stateType = @interface.GenericTypeArguments[1]; - var iActionHandlerServiceType = handlerType.MakeGenericType(actionType); - var updaterHandlerImplementationType = updaterHandler.MakeGenericType(stateType, actionType); - builder.Services.AddSingleton(iActionHandlerServiceType, updaterHandlerImplementationType); + var iMiddlewareServiceType = handlerType.MakeGenericType(actionType, stateType); + var updaterMiddlewareImplementationType = updaterHandler.MakeGenericType(stateType, actionType); + builder.Services.AddSingleton(iMiddlewareServiceType, updaterMiddlewareImplementationType); // Equivalent to: AddSingleton, Updater>(); builder.Services.AddSingleton(@interface, updater); - Console.WriteLine($"- AddSingleton<{iActionHandlerServiceType.GetStatorName()}, {updaterHandlerImplementationType.GetStatorName()}>()"); + Console.WriteLine($"- AddSingleton<{iMiddlewareServiceType.GetStatorName()}, {updaterMiddlewareImplementationType.GetStatorName()}>()"); Console.WriteLine($"- AddSingleton<{@interface.GetStatorName()}, {updater.GetStatorName()}>()"); } } + // Register Middleware + foreach (var middleware in builder.Middlewares.AsEnumerable().Reverse()) + { + builder.Services.Decorate(typeof(IActionFilter<,>), middleware); + } + // Run post-configuration postConfiguration?.Invoke(builder); diff --git a/src/StateR/Store.cs b/src/StateR/Store.cs index 8d1e219..ad627df 100644 --- a/src/StateR/Store.cs +++ b/src/StateR/Store.cs @@ -13,7 +13,14 @@ public Store(IServiceProvider serviceProvider, IDispatcher dispatcher) _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); } - public Task DispatchAsync(TAction action, CancellationToken cancellationToken = default) where TAction : IAction + public Task DispatchAsync(TAction action, CancellationToken cancellationToken = default) + where TAction : IAction + where TState : StateBase + { + return _dispatcher.DispatchAsync(action, cancellationToken); + } + + public Task DispatchAsync(object action, CancellationToken cancellationToken) { return _dispatcher.DispatchAsync(action, cancellationToken); } diff --git a/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs b/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs deleted file mode 100644 index bcc066a..0000000 --- a/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace StateR.Updaters.Hooks; - -public interface IAfterUpdateHook -{ - Task AfterUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase; -} diff --git a/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs b/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs deleted file mode 100644 index 7bd32a7..0000000 --- a/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace StateR.Updaters.Hooks; - -public interface IBeforeUpdateHook -{ - Task BeforeUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase; -} diff --git a/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs b/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs deleted file mode 100644 index 1503e95..0000000 --- a/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace StateR.Updaters.Hooks; - -public interface IUpdateHooksCollection -{ - Task BeforeUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase; - Task AfterUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase; -} diff --git a/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs b/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs deleted file mode 100644 index e8c56d8..0000000 --- a/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace StateR.Updaters.Hooks; - -public class UpdateHooksCollection : IUpdateHooksCollection -{ - private readonly IEnumerable _beforeUpdateHooks; - private readonly IEnumerable _afterUpdateHooks; - public UpdateHooksCollection(IEnumerable beforeUpdateHooks, IEnumerable afterUpdateHooks) - { - _beforeUpdateHooks = beforeUpdateHooks ?? throw new ArgumentNullException(nameof(beforeUpdateHooks)); - _afterUpdateHooks = afterUpdateHooks ?? throw new ArgumentNullException(nameof(afterUpdateHooks)); - } - - public async Task BeforeUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase - { - foreach (var hook in _beforeUpdateHooks) - { - await hook.BeforeUpdateAsync(context, state, updater, cancellationToken); - } - } - - public async Task AfterUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase - { - foreach (var hook in _afterUpdateHooks) - { - await hook.AfterUpdateAsync(context, state, updater, cancellationToken); - } - } -} diff --git a/src/StateR/Updaters/IUpdater.cs b/src/StateR/Updaters/IUpdater.cs index ddeacdc..6882cd0 100644 --- a/src/StateR/Updaters/IUpdater.cs +++ b/src/StateR/Updaters/IUpdater.cs @@ -1,7 +1,7 @@ namespace StateR.Updaters; public interface IUpdater - where TAction : IAction + where TAction : IAction where TState : StateBase { TState Update(TAction action, TState state); diff --git a/src/StateR/Updaters/UpdaterActionHandler.cs b/src/StateR/Updaters/UpdaterActionHandler.cs deleted file mode 100644 index 33efa7b..0000000 --- a/src/StateR/Updaters/UpdaterActionHandler.cs +++ /dev/null @@ -1,40 +0,0 @@ -using StateR.ActionHandlers; -using StateR.Updaters.Hooks; - -namespace StateR.Updaters; - -public class UpdaterActionHandler : IActionHandler - where TState : StateBase - where TAction : IAction -{ - private readonly IUpdateHooksCollection _hooks; - private readonly IEnumerable> _updaters; - private readonly IState _state; - - public UpdaterActionHandler(IState state, IEnumerable> updaters, IUpdateHooksCollection hooks) - { - _state = state ?? throw new ArgumentNullException(nameof(state)); - _updaters = updaters ?? throw new ArgumentNullException(nameof(updaters)); - _hooks = hooks ?? throw new ArgumentNullException(nameof(hooks)); - } - - public async Task HandleAsync(IDispatchContext context, CancellationToken cancellationToken) - { - foreach (var updater in _updaters) - { - if (cancellationToken.IsCancellationRequested) - { - break; - } - await _hooks.BeforeUpdateAsync(context, _state, updater, cancellationToken); - if (cancellationToken.IsCancellationRequested) - { - break; - } - _state.Set(updater.Update(context.Action, _state.Current)); - await _hooks.AfterUpdateAsync(context, _state, updater, cancellationToken); - } - _state.Notify(); - cancellationToken.ThrowIfCancellationRequested(); - } -} diff --git a/src/StateR/Updaters/UpdaterMiddleware.cs b/src/StateR/Updaters/UpdaterMiddleware.cs new file mode 100644 index 0000000..60183a3 --- /dev/null +++ b/src/StateR/Updaters/UpdaterMiddleware.cs @@ -0,0 +1,34 @@ +using StateR.Pipeline; + +namespace StateR.Updaters; + +public class UpdaterMiddleware : IActionFilter + where TState : StateBase + where TAction : IAction +{ + private readonly IEnumerable> _updaters; + private readonly IState _state; + + public UpdaterMiddleware(IState state, IEnumerable> updaters) + { + _state = state ?? throw new ArgumentNullException(nameof(state)); + _updaters = updaters ?? throw new ArgumentNullException(nameof(updaters)); + } + + public Task InvokeAsync(IDispatchContext context, ActionDelegate? next, CancellationToken cancellationToken) + { + foreach (var updater in _updaters) + { + if (cancellationToken.IsCancellationRequested) + { + break; + } + _state.Set(updater.Update(context.Action, _state.Current)); + } + _state.Notify(); + cancellationToken.ThrowIfCancellationRequested(); + + next?.Invoke(context, cancellationToken); + return Task.CompletedTask; + } +} diff --git a/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs b/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs index cd65dba..fbca015 100644 --- a/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs +++ b/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs @@ -10,7 +10,7 @@ public class UpdaterActionHandlerTest private readonly Mock> _stateMock = new(); private readonly List> _updaters = new(); private readonly Mock _hooksMock = new(); - private readonly UpdaterActionHandler sut; + private readonly UpdaterMiddleware sut; private readonly Queue _operationQueue = new(); private readonly TestAction _action = new(); private readonly DispatchContext _context; From 03f5c53a860ac9b4b334b9cad00e1a2c2a9e95ae Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Tue, 1 Mar 2022 19:56:37 -0500 Subject: [PATCH 02/17] wip --- .../CounterApp/CounterApp/CounterApp.csproj | 1 + .../CounterApp/CounterApp/Features/Counter.cs | 46 +++++++------- samples/CounterApp/CounterApp/Program.cs | 10 +-- .../AsyncLogic/AsyncError.cs | 4 +- .../StateR.Experiments.csproj | 18 ++++++ .../FluentValidation/StartupExtensions.cs | 4 +- .../StateValidationDecorator.cs | 62 +++++++++---------- ...tionInterceptor.cs => ValidationFilter.cs} | 32 +++++++--- src/StateR/IDispatchManager.cs | 8 --- src/StateR/IStatorBuilder.cs | 7 +++ src/StateR/Internal/StatorBuilder.cs | 6 ++ src/StateR/StatorStartupExtensions.cs | 13 +++- 12 files changed, 128 insertions(+), 83 deletions(-) rename src/StateR.Experiments/Validations/FluentValidation/{ValidationInterceptor.cs => ValidationFilter.cs} (69%) delete mode 100644 src/StateR/IDispatchManager.cs diff --git a/samples/CounterApp/CounterApp/CounterApp.csproj b/samples/CounterApp/CounterApp/CounterApp.csproj index 98b8158..74fa298 100644 --- a/samples/CounterApp/CounterApp/CounterApp.csproj +++ b/samples/CounterApp/CounterApp/CounterApp.csproj @@ -20,6 +20,7 @@ + diff --git a/samples/CounterApp/CounterApp/Features/Counter.cs b/samples/CounterApp/CounterApp/Features/Counter.cs index e9ce5cc..3532ade 100644 --- a/samples/CounterApp/CounterApp/Features/Counter.cs +++ b/samples/CounterApp/CounterApp/Features/Counter.cs @@ -1,4 +1,4 @@ -//using FluentValidation; +using FluentValidation; using StateR; //using StateR.Blazor.Persistance; using StateR.Updaters; @@ -32,28 +32,28 @@ public State Update(SetNegative action, State state) => state with { Count = action.Count }; } - //public class SetPositiveValidator : AbstractValidator - //{ - // public SetPositiveValidator() - // { - // RuleFor(x => x.Count).GreaterThan(0); - // } - //} + public class SetPositiveValidator : AbstractValidator + { + public SetPositiveValidator() + { + RuleFor(x => x.Count).GreaterThan(0); + } + } - //public class SetNegativeValidator : AbstractValidator - //{ - // public SetNegativeValidator() - // { - // RuleFor(x => x.Count).LessThan(0); - // } - //} + public class SetNegativeValidator : AbstractValidator + { + public SetNegativeValidator() + { + RuleFor(x => x.Count).LessThan(0); + } + } - //public class StateValidator : AbstractValidator - //{ - // public StateValidator() - // { - // RuleFor(x => x.Count).GreaterThan(-100); - // RuleFor(x => x.Count).LessThan(100); - // } - //} + public class StateValidator : AbstractValidator + { + public StateValidator() + { + RuleFor(x => x.Count).GreaterThan(-100); + RuleFor(x => x.Count).LessThan(100); + } + } } \ No newline at end of file diff --git a/samples/CounterApp/CounterApp/Program.cs b/samples/CounterApp/CounterApp/Program.cs index 36530f0..9ee5a23 100644 --- a/samples/CounterApp/CounterApp/Program.cs +++ b/samples/CounterApp/CounterApp/Program.cs @@ -6,7 +6,7 @@ //using StateR.Blazor.ReduxDevTools; using ForEvolve.Blazor.WebStorage; //using StateR.Experiments.AsyncLogic; -//using StateR.Validations.FluentValidation; +using StateR.Validations.FluentValidation; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); @@ -42,11 +42,11 @@ public static void RegisterServices(this IServiceCollection services) .AddStateR(appAssembly) //.AddAsyncOperations() //.AddReduxDevTools() - //.AddFluentValidation(appAssembly) - .Apply(/*buidler => buidler - .AddPersistence() + .AddFluentValidation(appAssembly) + .Apply(buidler => buidler + //.AddPersistence() .AddStateValidation() - */) + ) ; services.AddSingleton(sp => new HttpClient { BaseAddress = new Uri(sp.GetRequiredService().BaseAddress) }); } diff --git a/src/StateR.Experiments/AsyncLogic/AsyncError.cs b/src/StateR.Experiments/AsyncLogic/AsyncError.cs index a160565..5a97a8b 100644 --- a/src/StateR.Experiments/AsyncLogic/AsyncError.cs +++ b/src/StateR.Experiments/AsyncLogic/AsyncError.cs @@ -6,7 +6,7 @@ public class AsyncError { public record State : StateBase { - public IAction? Action { get; init; } + public IAction? Action { get; init; } public AsyncState? InitialState { get; init; } public AsyncState? ActualState { get; init; } public Exception? Exception { get; init; } @@ -21,7 +21,7 @@ public class InitialState : IInitialState public State Value => new(); } - public record Occured(IAction Action, AsyncState InitialState, AsyncState ActualState, Exception Exception) : IAction; + public record Occured(IAction Action, AsyncState InitialState, AsyncState ActualState, Exception Exception) : IAction; public class Updaters : IUpdater { diff --git a/src/StateR.Experiments/StateR.Experiments.csproj b/src/StateR.Experiments/StateR.Experiments.csproj index 95009eb..0cf5587 100644 --- a/src/StateR.Experiments/StateR.Experiments.csproj +++ b/src/StateR.Experiments/StateR.Experiments.csproj @@ -5,6 +5,24 @@ StateR + + + + + + + + + + + + + + + + + + diff --git a/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs b/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs index d01fa92..18df8c8 100644 --- a/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs +++ b/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs @@ -20,10 +20,12 @@ public static IStatorBuilder AddFluentValidation(this IStatorBuilder builder, pa typeof(ValidationUpdaters), typeof(ValidationInitialState), typeof(ValidationState), + typeof(ValidationFilter<,>), }); + builder.AddMiddlewares(new[] { typeof(ValidationFilter<,>) }); // Validation interceptor and state - builder.Services.TryAddSingleton(typeof(IActionFilter<,>), typeof(ValidationInterceptor<>)); + builder.Services.TryAddSingleton(typeof(IActionFilter<,>), typeof(ValidationFilter<,>)); // Scan for validators builder.Services.AddValidatorsFromAssemblies(assembliesToScan, ServiceLifetime.Singleton); diff --git a/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs b/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs index 91a8a2b..9468541 100644 --- a/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs +++ b/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs @@ -1,10 +1,7 @@ using FluentValidation; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using StateR.ActionHandlers; using StateR.Internal; -using System; -using System.Reflection; namespace StateR.Validations.FluentValidation; @@ -54,15 +51,16 @@ public static class StateValidatorStartupExtensions public static IStatorBuilder AddStateValidation(this IStatorBuilder builder) { RegisterStateDecorator(builder.Services, builder.All); - ActionHandlerDecorator(builder.Services); + //ActionHandlerDecorator(builder.Services); return builder; } - private static void ActionHandlerDecorator(IServiceCollection services) - { - Console.WriteLine("- Decorate, ValidationExceptionActionHandlersManagerDecorator>()"); - services.Decorate(); - } + //private static void ActionHandlerDecorator(IServiceCollection services) + //{ + // Console.WriteLine("- Decorate, ValidationExceptionActionHandlersManagerDecorator>()"); + // services.Decorate(); + //} + private static void RegisterStateDecorator(IServiceCollection services, IEnumerable types) { var states = TypeScanner.FindStates(types); @@ -79,27 +77,27 @@ private static void RegisterStateDecorator(IServiceCollection services, IEnumera } } -public class ValidationExceptionActionHandlersManagerDecorator : IActionHandlersManager -{ - private readonly IActionHandlersManager _next; - public ValidationExceptionActionHandlersManagerDecorator(IActionHandlersManager next) - { - _next = next ?? throw new ArgumentNullException(nameof(next)); - } +//public class ValidationExceptionActionHandlersManagerDecorator : IActionFilter +//{ +// private readonly IActionHandlersManager _next; +// public ValidationExceptionActionHandlersManagerDecorator(IActionHandlersManager next) +// { +// _next = next ?? throw new ArgumentNullException(nameof(next)); +// } - public async Task DispatchAsync(IDispatchContext dispatchContext) - where TAction : IAction - { - try - { - await _next.DispatchAsync(dispatchContext); - } - catch (ValidationException ex) - { - await dispatchContext.Dispatcher.DispatchAsync( - new AddValidationErrors(ex.Errors), - dispatchContext.CancellationToken - ); - } - } -} +// public async Task DispatchAsync(IDispatchContext dispatchContext) +// where TAction : IAction +// { +// try +// { +// await _next.DispatchAsync(dispatchContext); +// } +// catch (ValidationException ex) +// { +// await dispatchContext.Dispatcher.DispatchAsync( +// new AddValidationErrors(ex.Errors), +// dispatchContext.CancellationToken +// ); +// } +// } +//} diff --git a/src/StateR.Experiments/Validations/FluentValidation/ValidationInterceptor.cs b/src/StateR.Experiments/Validations/FluentValidation/ValidationFilter.cs similarity index 69% rename from src/StateR.Experiments/Validations/FluentValidation/ValidationInterceptor.cs rename to src/StateR.Experiments/Validations/FluentValidation/ValidationFilter.cs index 9a140b9..9c836c3 100644 --- a/src/StateR.Experiments/Validations/FluentValidation/ValidationInterceptor.cs +++ b/src/StateR.Experiments/Validations/FluentValidation/ValidationFilter.cs @@ -1,22 +1,25 @@ using FluentValidation; using FluentValidation.Results; -using StateR.Interceptors; +using StateR.Pipeline; using StateR.Updaters; using System.Collections.Immutable; namespace StateR.Validations.FluentValidation; -public class ValidationInterceptor : IInterceptor - where TAction : IAction +public class ValidationFilter : IActionFilter + where TAction : IAction + where TState : StateBase { private readonly IEnumerable> _validators; - public ValidationInterceptor(IEnumerable> validators) + public ValidationFilter(IEnumerable> validators) { _validators = validators; } - public async Task InterceptAsync(IDispatchContext context, CancellationToken cancellationToken) + public async Task InvokeAsync(IDispatchContext context, ActionDelegate? next, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(next, nameof(next)); + var result = _validators .Select(validator => validator.Validate(context.Action)); if (result?.Any(validator => !validator.IsValid) ?? false) @@ -27,6 +30,17 @@ public async Task InterceptAsync(IDispatchContext context, Cancellation await context.Dispatcher.DispatchAsync(new AddValidationErrors(errors), cancellationToken); context.Cancel(); } + try + { + await next(context, cancellationToken); + } + catch (ValidationException ex) + { + await context.Dispatcher.DispatchAsync( + new AddValidationErrors(ex.Errors), + context.CancellationToken + ); + } } } @@ -39,10 +53,10 @@ public class ValidationInitialState : IInitialState public ValidationState Value => new(ImmutableList.Create()); } -public record class AddValidationErrors(IEnumerable Errors) : IAction; -public record class ReplaceValidationErrors(IEnumerable Errors) : IAction; -public record class CleanValidationError() : IAction; -public record class RemoveValidationError(ValidationFailure Error) : IAction; +public record class AddValidationErrors(IEnumerable Errors) : IAction; +public record class ReplaceValidationErrors(IEnumerable Errors) : IAction; +public record class CleanValidationError() : IAction; +public record class RemoveValidationError(ValidationFailure Error) : IAction; public class ValidationUpdaters : IUpdater, diff --git a/src/StateR/IDispatchManager.cs b/src/StateR/IDispatchManager.cs deleted file mode 100644 index 70b1c50..0000000 --- a/src/StateR/IDispatchManager.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace StateR; - -public interface IDispatchManager -{ - Task DispatchAsync(IDispatchContext dispatchContext) - where TAction : IAction - where TState : StateBase; -} diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index f7605af..3c1b3b3 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -22,4 +22,11 @@ public interface IStatorBuilder IStatorBuilder AddMiddlewares(IEnumerable types); List Middlewares { get; } + + IStatorBuilder AddState() + where TState : StateBase; + + //IStatorBuilder AddAction() + // where TState : StateBase + // where TAction : IAction; } diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index 9148111..4898d31 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -39,4 +39,10 @@ private IStatorBuilder AddDistinctTypes(List list, IEnumerable types list.AddRange(distinctTypes); return this; } + + public IStatorBuilder AddState() where TState : StateBase + { + States.Add(typeof(TState)); + return this; + } } diff --git a/src/StateR/StatorStartupExtensions.cs b/src/StateR/StatorStartupExtensions.cs index 5c64773..cac8df4 100644 --- a/src/StateR/StatorStartupExtensions.cs +++ b/src/StateR/StatorStartupExtensions.cs @@ -28,12 +28,17 @@ public static IStatorBuilder AddStateR(this IServiceCollection services) return new StatorBuilder(services); } - public static IStatorBuilder AddStateR(this IServiceCollection services, params Assembly[] assembliesToScan) + public static IStatorBuilder AddStateR(this IServiceCollection services, params Assembly[] assembliesToScanForStates) { var builder = services.AddStateR(); - var allTypes = assembliesToScan + var allTypes = assembliesToScanForStates .SelectMany(a => a.GetTypes()); - return builder.AddTypes(allTypes); + //builder.AddTypes(allTypes); + + var states = TypeScanner.FindStates(allTypes); + builder.AddStates(states); + + return builder; } //public static IStatorBuilder AddMiddleware(this IStatorBuilder builder) @@ -43,6 +48,8 @@ public static IStatorBuilder AddStateR(this IServiceCollection services, params public static IServiceCollection Apply(this IStatorBuilder builder, Action? postConfiguration = null) { + return builder.Services; + // Extract types builder.ScanTypes(); From c8d121d01ac7059b709d8855393012aa27d6f5f6 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Tue, 1 Mar 2022 20:00:34 -0500 Subject: [PATCH 03/17] Remove old tests --- .../ActionHandlerManagerTest.cs | 116 ------------ .../Hooks/ActionHandlerHooksCollectionTest.cs | 60 ------ .../AfterEffects/AfterEffectsManagerTest.cs | 116 ------------ .../Hooks/AfterEffectHooksCollectionTest.cs | 61 ------ test/StateR.Tests/DispatcherTest.cs | 82 -------- .../Hooks/InterceptorsHooksCollectionTest.cs | 60 ------ .../Interceptors/InterceptorsManagerTest.cs | 114 ----------- .../Hooks/UpdateHooksCollectionTest.cs | 65 ------- .../Reducers/UpdaterActionHandlerTest.cs | 178 ------------------ 9 files changed, 852 deletions(-) delete mode 100644 test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs delete mode 100644 test/StateR.Tests/ActionHandlers/Hooks/ActionHandlerHooksCollectionTest.cs delete mode 100644 test/StateR.Tests/AfterEffects/AfterEffectsManagerTest.cs delete mode 100644 test/StateR.Tests/AfterEffects/Hooks/AfterEffectHooksCollectionTest.cs delete mode 100644 test/StateR.Tests/DispatcherTest.cs delete mode 100644 test/StateR.Tests/Interceptors/Hooks/InterceptorsHooksCollectionTest.cs delete mode 100644 test/StateR.Tests/Interceptors/InterceptorsManagerTest.cs delete mode 100644 test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs delete mode 100644 test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs diff --git a/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs b/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs deleted file mode 100644 index ff259c1..0000000 --- a/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs +++ /dev/null @@ -1,116 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Moq; -using StateR.ActionHandlers.Hooks; -using Xunit; - -namespace StateR.ActionHandlers; - -public class ActionHandlerManagerTest -{ - private readonly Mock _hooksCollectionMock = new(); - - protected ActionHandlersManager CreateUpdatersManager(Action configureServices) - { - var services = new ServiceCollection(); - configureServices?.Invoke(services); - var serviceProvider = services.BuildServiceProvider(); - return new ActionHandlersManager(_hooksCollectionMock.Object, serviceProvider); - } - - public class DispatchAsync : ActionHandlerManagerTest - { - [Fact] - public async Task Should_call_all_action_handlers() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var handler1 = new Mock>(); - var handler2 = new Mock>(); - var sut = CreateUpdatersManager(services => - { - services.AddSingleton(handler1.Object); - services.AddSingleton(handler2.Object); - }); - - // Act - await sut.DispatchAsync(context); - - // Assert - handler1.Verify(x => x.HandleAsync(context, cancellationTokenSource.Token), Times.Once); - handler2.Verify(x => x.HandleAsync(context, cancellationTokenSource.Token), Times.Once); - } - - [Fact] - public async Task Should_break_handlers_when_Cancel() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var afterEffect1 = new Mock>(); - afterEffect1.Setup(x => x.HandleAsync(context, cancellationTokenSource.Token)) - .Callback((IDispatchContext context, CancellationToken cancellationToken) - => context.Cancel()); - var afterEffect2 = new Mock>(); - var sut = CreateUpdatersManager(services => - { - services.AddSingleton(afterEffect1.Object); - services.AddSingleton(afterEffect2.Object); - }); - - // Act - await Assert.ThrowsAsync(() - => sut.DispatchAsync(context)); - - // Assert - afterEffect1.Verify(x => x.HandleAsync(context, cancellationTokenSource.Token), Times.Once); - afterEffect2.Verify(x => x.HandleAsync(context, cancellationTokenSource.Token), Times.Never); - } - - [Fact] - public async Task Should_call_middleware_and_handlers_in_order() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var operationQueue = new Queue(); - var actionHandler1 = new Mock>(); - actionHandler1.Setup(x => x.HandleAsync(context, cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("actionHandler1.HandleAsync")); - var actionHandler2 = new Mock>(); - actionHandler2.Setup(x => x.HandleAsync(context, cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("actionHandler2.HandleAsync")); - _hooksCollectionMock - .Setup(x => x.BeforeHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("BeforeHandlerAsync")); - _hooksCollectionMock - .Setup(x => x.AfterHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("AfterHandlerAsync")); - - var sut = CreateUpdatersManager(services => - { - services.AddSingleton(actionHandler1.Object); - services.AddSingleton(actionHandler2.Object); - }); - - // Act - await sut.DispatchAsync(context); - - // Assert - Assert.Collection(operationQueue, - op => Assert.Equal("BeforeHandlerAsync", op), - op => Assert.Equal("actionHandler1.HandleAsync", op), - op => Assert.Equal("AfterHandlerAsync", op), - - op => Assert.Equal("BeforeHandlerAsync", op), - op => Assert.Equal("actionHandler2.HandleAsync", op), - op => Assert.Equal("AfterHandlerAsync", op) - ); - } - } - - public record TestAction : IAction; -} diff --git a/test/StateR.Tests/ActionHandlers/Hooks/ActionHandlerHooksCollectionTest.cs b/test/StateR.Tests/ActionHandlers/Hooks/ActionHandlerHooksCollectionTest.cs deleted file mode 100644 index 50a60fb..0000000 --- a/test/StateR.Tests/ActionHandlers/Hooks/ActionHandlerHooksCollectionTest.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Moq; -using Xunit; - -namespace StateR.ActionHandlers.Hooks; - -public class ActionHandlerHooksCollectionTest -{ - private readonly Mock _before1Mock = new(); - private readonly Mock _before2Mock = new(); - private readonly Mock _after1Mock = new(); - private readonly Mock _after2Mock = new(); - - private readonly Mock> _afterEffectMock = new(); - private readonly IDispatchContext dispatchContext; - private readonly CancellationToken _cancellationToken = CancellationToken.None; - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - - private readonly ActionHandlerHooksCollection sut; - - public ActionHandlerHooksCollectionTest() - { - dispatchContext = new DispatchContext(new TestAction(), new Mock().Object, _cancellationTokenSource); - sut = new( - new[] { _before1Mock.Object, _before2Mock.Object }, - new[] { _after1Mock.Object, _after2Mock.Object } - ); - } - public class BeforeHandlerAsync : ActionHandlerHooksCollectionTest - { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken); - - // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _before2Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after1Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after2Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - } - } - - public class AfterHandlerAsync : ActionHandlerHooksCollectionTest - { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken); - - // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _before2Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after1Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after2Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - } - } - public record TestAction : IAction; -} diff --git a/test/StateR.Tests/AfterEffects/AfterEffectsManagerTest.cs b/test/StateR.Tests/AfterEffects/AfterEffectsManagerTest.cs deleted file mode 100644 index bd9a9b1..0000000 --- a/test/StateR.Tests/AfterEffects/AfterEffectsManagerTest.cs +++ /dev/null @@ -1,116 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Moq; -using StateR.AfterEffects.Hooks; -using Xunit; - -namespace StateR.AfterEffects; - -public class AfterEffectsManagerTest -{ - private readonly Mock _afterEffectHooksCollectionMock = new(); - protected AfterEffectsManager CreateAfterEffectsManager(Action configureServices) - { - var services = new ServiceCollection(); - configureServices?.Invoke(services); - services.TryAddSingleton(_afterEffectHooksCollectionMock.Object); - var serviceProvider = services.BuildServiceProvider(); - var afterEffectHooksCollection = serviceProvider.GetService(); - return new AfterEffectsManager(afterEffectHooksCollection, serviceProvider); - } - - public class DispatchAsync : AfterEffectsManagerTest - { - [Fact] - public async Task Should_handle_all_after_effects() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var afterEffect1 = new Mock>(); - var afterEffect2 = new Mock>(); - var sut = CreateAfterEffectsManager(services => - { - services.AddSingleton(afterEffect1.Object); - services.AddSingleton(afterEffect2.Object); - }); - - // Act - await sut.DispatchAsync(context); - - // Assert - afterEffect1.Verify(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token), Times.Once); - afterEffect2.Verify(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token), Times.Once); - } - - [Fact] - public async Task Should_break_after_effects_when_Cancel() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var afterEffect1 = new Mock>(); - afterEffect1.Setup(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token)) - .Callback((IDispatchContext context, CancellationToken cancellationToken) => context.Cancel()); - var afterEffect2 = new Mock>(); - var sut = CreateAfterEffectsManager(services => - { - services.AddSingleton(afterEffect1.Object); - services.AddSingleton(afterEffect2.Object); - }); - - // Act - await Assert.ThrowsAsync(() - => sut.DispatchAsync(context)); - - // Assert - afterEffect1.Verify(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token), Times.Once); - afterEffect2.Verify(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token), Times.Never); - } - - [Fact] - public async Task Should_call_hooks_and_after_effects_methods_in_order() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var operationQueue = new Queue(); - var afterEffect1 = new Mock>(); - afterEffect1.Setup(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("afterEffect1.HandleAfterEffectAsync")); - var afterEffect2 = new Mock>(); - afterEffect2.Setup(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("afterEffect2.HandleAfterEffectAsync")); - _afterEffectHooksCollectionMock - .Setup(x => x.BeforeHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("BeforeHandlerAsync")); - _afterEffectHooksCollectionMock - .Setup(x => x.AfterHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("AfterHandlerAsync")); - var sut = CreateAfterEffectsManager(services => - { - services.AddSingleton(afterEffect1.Object); - services.AddSingleton(afterEffect2.Object); - }); - - // Act - await sut.DispatchAsync(context); - - // Assert - Assert.Collection(operationQueue, - op => Assert.Equal("BeforeHandlerAsync", op), - op => Assert.Equal("afterEffect1.HandleAfterEffectAsync", op), - op => Assert.Equal("AfterHandlerAsync", op), - - op => Assert.Equal("BeforeHandlerAsync", op), - op => Assert.Equal("afterEffect2.HandleAfterEffectAsync", op), - op => Assert.Equal("AfterHandlerAsync", op) - ); - } - } - - public record TestAction : IAction; -} diff --git a/test/StateR.Tests/AfterEffects/Hooks/AfterEffectHooksCollectionTest.cs b/test/StateR.Tests/AfterEffects/Hooks/AfterEffectHooksCollectionTest.cs deleted file mode 100644 index baa3fea..0000000 --- a/test/StateR.Tests/AfterEffects/Hooks/AfterEffectHooksCollectionTest.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Moq; -using Xunit; - -namespace StateR.AfterEffects.Hooks; - -public class AfterEffectHooksCollectionTest -{ - private readonly Mock _before1Mock = new(); - private readonly Mock _before2Mock = new(); - private readonly Mock _after1Mock = new(); - private readonly Mock _after2Mock = new(); - - private readonly Mock> _afterEffectMock = new(); - private readonly IDispatchContext _dispatchContext; - private readonly CancellationToken _cancellationToken = CancellationToken.None; - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - - private readonly AfterEffectHooksCollection sut; - - public AfterEffectHooksCollectionTest() - { - _dispatchContext = new DispatchContext(new TestAction(), new Mock().Object, _cancellationTokenSource); - sut = new( - new[] { _before1Mock.Object, _before2Mock.Object }, - new[] { _after1Mock.Object, _after2Mock.Object } - ); - } - public class BeforeHandlerAsync : AfterEffectHooksCollectionTest - { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken); - - // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _before2Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after1Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after2Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - } - - } - - public class AfterHandlerAsync : AfterEffectHooksCollectionTest - { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken); - - // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _before2Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after1Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after2Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - } - } - public record TestAction : IAction; -} diff --git a/test/StateR.Tests/DispatcherTest.cs b/test/StateR.Tests/DispatcherTest.cs deleted file mode 100644 index 42c81d3..0000000 --- a/test/StateR.Tests/DispatcherTest.cs +++ /dev/null @@ -1,82 +0,0 @@ -using Microsoft.Extensions.Logging; -using Moq; -using StateR.ActionHandlers; -using StateR.AfterEffects; -using StateR.Interceptors; -using Xunit; - -namespace StateR; - -public class DispatcherTest -{ - private readonly Mock _dispatchContextFactory = new(); - private readonly Mock _interceptorsManager = new(); - private readonly Mock _actionHandlersManager = new(); - private readonly Mock _afterEffectsManager = new(); - private readonly Mock> _loggerMock = new(); - private readonly Dispatcher sut; - - public DispatcherTest() - { - sut = new(_dispatchContextFactory.Object, _interceptorsManager.Object, _actionHandlersManager.Object, _afterEffectsManager.Object, _loggerMock.Object); - } - - public class DispatchAsync : DispatcherTest - { - [Fact] - public async Task Should_create_DispatchContext_using_dispatchContextFactory() - { - var action = new TestAction(); - await sut.DispatchAsync(action, CancellationToken.None); - _dispatchContextFactory - .Verify(x => x.Create(action, sut, It.IsAny()), Times.Once); - } - - [Fact] - public async Task Should_send_the_same_DispatchContext_to_all_managers() - { - // Arrange - var action = new TestAction(); - var context = new DispatchContext(action, new Mock().Object, new CancellationTokenSource()); - _dispatchContextFactory - .Setup(x => x.Create(action, sut, It.IsAny())) - .Returns(context); - - // Act - await sut.DispatchAsync(action, CancellationToken.None); - - // Assert - _interceptorsManager.Verify(x => x.DispatchAsync(context), Times.Once); - _actionHandlersManager.Verify(x => x.DispatchAsync(context), Times.Once); - _afterEffectsManager.Verify(x => x.DispatchAsync(context), Times.Once); - } - [Fact] - public async Task Should_call_managers_in_the_expected_order() - { - // Arrange - var action = new TestAction(); - var operationQueue = new Queue(); - _interceptorsManager - .Setup(x => x.DispatchAsync(It.IsAny>())) - .Callback(() => operationQueue.Enqueue("Interceptors")); - _actionHandlersManager - .Setup(x => x.DispatchAsync(It.IsAny>())) - .Callback(() => operationQueue.Enqueue("Updaters")); - _afterEffectsManager - .Setup(x => x.DispatchAsync(It.IsAny>())) - .Callback(() => operationQueue.Enqueue("AfterEffects")); - - // Act - await sut.DispatchAsync(action, CancellationToken.None); - - // Assert - Assert.Collection(operationQueue, - operation => Assert.Equal("Interceptors", operation), - operation => Assert.Equal("Updaters", operation), - operation => Assert.Equal("AfterEffects", operation) - ); - } - } - - private record TestAction : IAction; -} diff --git a/test/StateR.Tests/Interceptors/Hooks/InterceptorsHooksCollectionTest.cs b/test/StateR.Tests/Interceptors/Hooks/InterceptorsHooksCollectionTest.cs deleted file mode 100644 index b9cb112..0000000 --- a/test/StateR.Tests/Interceptors/Hooks/InterceptorsHooksCollectionTest.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Moq; -using Xunit; - -namespace StateR.Interceptors.Hooks; - -public class InterceptorsHooksCollectionTest -{ - private readonly Mock _before1Mock = new(); - private readonly Mock _before2Mock = new(); - private readonly Mock _after1Mock = new(); - private readonly Mock _after2Mock = new(); - - private readonly Mock> _afterEffectMock = new(); - private readonly IDispatchContext _dispatchContext; - private readonly CancellationToken _cancellationToken = CancellationToken.None; - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - - private readonly InterceptorsHooksCollection sut; - - public InterceptorsHooksCollectionTest() - { - _dispatchContext = new DispatchContext(new TestAction(), new Mock().Object, _cancellationTokenSource); - sut = new( - new[] { _before1Mock.Object, _before2Mock.Object }, - new[] { _after1Mock.Object, _after2Mock.Object } - ); - } - - public class BeforeHandlerAsync : InterceptorsHooksCollectionTest - { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken); - - // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _before2Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after1Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after2Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - } - } - public class AfterHandlerAsync : InterceptorsHooksCollectionTest - { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken); - - // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _before2Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after1Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after2Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - } - } - public record TestAction : IAction; -} diff --git a/test/StateR.Tests/Interceptors/InterceptorsManagerTest.cs b/test/StateR.Tests/Interceptors/InterceptorsManagerTest.cs deleted file mode 100644 index 09e4521..0000000 --- a/test/StateR.Tests/Interceptors/InterceptorsManagerTest.cs +++ /dev/null @@ -1,114 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Moq; -using StateR.Interceptors.Hooks; -using Xunit; - -namespace StateR.Interceptors; - -public class InterceptorsManagerTest -{ - private readonly Mock _hooksCollectionMock = new(); - protected InterceptorsManager CreateInterceptorsManager(Action configureServices) - { - var services = new ServiceCollection(); - configureServices?.Invoke(services); - services.TryAddSingleton(_hooksCollectionMock.Object); - var serviceProvider = services.BuildServiceProvider(); - return new InterceptorsManager(_hooksCollectionMock.Object, serviceProvider); - } - - public class DispatchAsync : InterceptorsManagerTest - { - [Fact] - public async Task Should_dispatch_to_all_interceptors() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var interceptor1 = new Mock>(); - var interceptor2 = new Mock>(); - var sut = CreateInterceptorsManager(services => - { - services.AddSingleton(interceptor1.Object); - services.AddSingleton(interceptor2.Object); - }); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - // Act - await sut.DispatchAsync(context); - - // Assert - interceptor1.Verify(x => x.InterceptAsync(context, cancellationTokenSource.Token), Times.Once); - interceptor2.Verify(x => x.InterceptAsync(context, cancellationTokenSource.Token), Times.Once); - } - - [Fact] - public async Task Should_call_middleware_and_interceptors_methods_in_order() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var operationQueue = new Queue(); - var interceptor1 = new Mock>(); - interceptor1.Setup(x => x.InterceptAsync(context, cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("interceptor1.InterceptAsync")); - var interceptor2 = new Mock>(); - interceptor2.Setup(x => x.InterceptAsync(context, cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("interceptor2.InterceptAsync")); - _hooksCollectionMock - .Setup(x => x.BeforeHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("BeforeHandlerAsync")); - _hooksCollectionMock - .Setup(x => x.AfterHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("AfterHandlerAsync")); - var sut = CreateInterceptorsManager(services => - { - services.AddSingleton(interceptor1.Object); - services.AddSingleton(interceptor2.Object); - }); - - // Act - await sut.DispatchAsync(context); - - // Assert - Assert.Collection(operationQueue, - op => Assert.Equal("BeforeHandlerAsync", op), - op => Assert.Equal("interceptor1.InterceptAsync", op), - op => Assert.Equal("AfterHandlerAsync", op), - - op => Assert.Equal("BeforeHandlerAsync", op), - op => Assert.Equal("interceptor2.InterceptAsync", op), - op => Assert.Equal("AfterHandlerAsync", op) - ); - } - - [Fact] - public async Task Should_break_interception_when_Cancel() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var interceptor1 = new Mock>(); - interceptor1.Setup(x => x.InterceptAsync(context, cancellationTokenSource.Token)) - .Callback((IDispatchContext context, CancellationToken cancellationToken) => context.Cancel()); - var interceptor2 = new Mock>(); - var sut = CreateInterceptorsManager(services => - { - services.AddSingleton(interceptor1.Object); - services.AddSingleton(interceptor2.Object); - }); - - // Act - await Assert.ThrowsAsync(() - => sut.DispatchAsync(context)); - - // Assert - interceptor1.Verify(x => x.InterceptAsync(context, cancellationTokenSource.Token), Times.Once); - interceptor2.Verify(x => x.InterceptAsync(context, cancellationTokenSource.Token), Times.Never); - } - } - - public record TestAction : IAction; -} diff --git a/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs b/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs deleted file mode 100644 index fd771ad..0000000 --- a/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Moq; -using Xunit; - -namespace StateR.Updaters.Hooks; - -public class UpdateHooksCollectionTest -{ - private readonly Mock _before1Mock = new(); - private readonly Mock _before2Mock = new(); - private readonly Mock _after1Mock = new(); - private readonly Mock _after2Mock = new(); - - private readonly Mock> _stateMock = new(); - private readonly Mock> _updater = new(); - - private readonly IDispatchContext _dispatchContext; - private readonly CancellationToken _cancellationToken = CancellationToken.None; - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - - private readonly UpdateHooksCollection sut; - - public UpdateHooksCollectionTest() - { - _dispatchContext = new DispatchContext(new TestAction(), new Mock().Object, _cancellationTokenSource); - sut = new UpdateHooksCollection( - new[] { _before1Mock.Object, _before2Mock.Object }, - new[] { _after1Mock.Object, _after2Mock.Object } - ); - } - - public class BeforeUpdateAsync : UpdateHooksCollectionTest - { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken); - - // Assert - _before1Mock.Verify(x => x.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Once); - _before2Mock.Verify(x => x.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Once); - _after1Mock.Verify(x => x.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Never); - _after2Mock.Verify(x => x.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Never); - } - } - - public class AfterUpdateAsync : UpdateHooksCollectionTest - { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken); - - // Assert - _before1Mock.Verify(x => x.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Never); - _before2Mock.Verify(x => x.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Never); - _after1Mock.Verify(x => x.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Once); - _after2Mock.Verify(x => x.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Once); - } - } - - public record TestAction : IAction; - public record TestState : StateBase; -} diff --git a/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs b/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs deleted file mode 100644 index fbca015..0000000 --- a/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs +++ /dev/null @@ -1,178 +0,0 @@ -using Moq; -using StateR.Updaters.Hooks; -using Xunit; - -namespace StateR.Updaters; - -public class UpdaterActionHandlerTest -{ - private readonly TestState _state = new(); - private readonly Mock> _stateMock = new(); - private readonly List> _updaters = new(); - private readonly Mock _hooksMock = new(); - private readonly UpdaterMiddleware sut; - private readonly Queue _operationQueue = new(); - private readonly TestAction _action = new(); - private readonly DispatchContext _context; - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - - private readonly Mock> _updater1Mock = new(); - private readonly Mock> _updater2Mock = new(); - - public UpdaterActionHandlerTest() - { - _context = new(_action, new Mock().Object, _cancellationTokenSource); - - _stateMock.Setup(x => x.Current).Returns(_state); - _stateMock.Setup(x => x.Notify()) - .Callback(() => _operationQueue.Enqueue("state.Notify")); - - _updater1Mock - .Setup(x => x.Update(_action, _state)) - .Returns(_state) - .Callback(() => _operationQueue.Enqueue("updater1.Update")); - _updaters.Add(_updater1Mock.Object); - _updater2Mock - .Setup(x => x.Update(_action, _state)) - .Returns(_state) - .Callback(() => _operationQueue.Enqueue("updater2.Update")); - _updaters.Add(_updater2Mock.Object); - - sut = new UpdaterActionHandler( - _stateMock.Object, - _updaters, - _hooksMock.Object - ); - } - - public class HandleAsync : UpdaterActionHandlerTest - { - [Fact] - public async Task Should_call_updaters_then_notify() - { - // Act - await sut.HandleAsync(_context, CancellationToken.None); - - // Assert - Assert.Collection(_operationQueue, - op => Assert.Equal("updater1.Update", op), - op => Assert.Equal("updater2.Update", op), - op => Assert.Equal("state.Notify", op) - ); - } - - [Fact] - public async Task Should_break_updates_when_Cancel_is_called_in_a_BeforeUpdateAsync_hook() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - _hooksMock - .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater1")); - _hooksMock - .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => - { - _operationQueue.Enqueue("BeforeUpdaterAsync:Updater2"); - _cancellationTokenSource.Cancel(); - }); - _hooksMock - .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater1")); - _hooksMock - .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater2")); - - // Act - await Assert.ThrowsAsync(() - => sut.HandleAsync(_context, _cancellationTokenSource.Token)); - - // Assert - Assert.Collection(_operationQueue, - op => Assert.Equal("BeforeUpdaterAsync:Updater1", op), - op => Assert.Equal("updater1.Update", op), - op => Assert.Equal("AfterUpdaterAsync:Updater1", op), - - op => Assert.Equal("BeforeUpdaterAsync:Updater2", op), - - op => Assert.Equal("state.Notify", op) - ); - } - - [Fact] - public async Task Should_break_updates_when_Cancel_is_called_in_an_AfterUpdateAsync_hook() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - _hooksMock - .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater1")); - _hooksMock - .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater2")); - _hooksMock - .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => - { - _operationQueue.Enqueue("AfterUpdaterAsync:Updater1"); - _cancellationTokenSource.Cancel(); - }); - _hooksMock - .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater2")); - - // Act - await Assert.ThrowsAsync(() - => sut.HandleAsync(_context, _cancellationTokenSource.Token)); - - // Assert - Assert.Collection(_operationQueue, - op => Assert.Equal("BeforeUpdaterAsync:Updater1", op), - op => Assert.Equal("updater1.Update", op), - op => Assert.Equal("AfterUpdaterAsync:Updater1", op), - - op => Assert.Equal("state.Notify", op) - ); - } - - [Fact] - public async Task Should_call_hooks_methods_in_order() - { - // Arrange - _hooksMock - .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, CancellationToken.None)) - .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater1")); - _hooksMock - .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, CancellationToken.None)) - .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater2")); - _hooksMock - .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, CancellationToken.None)) - .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater1")); - _hooksMock - .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, CancellationToken.None)) - .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater2")); - - // Act - await sut.HandleAsync(_context, CancellationToken.None); - - // Assert - Assert.Collection(_operationQueue, - op => Assert.Equal("BeforeUpdaterAsync:Updater1", op), - op => Assert.Equal("updater1.Update", op), - op => Assert.Equal("AfterUpdaterAsync:Updater1", op), - - op => Assert.Equal("BeforeUpdaterAsync:Updater2", op), - op => Assert.Equal("updater2.Update", op), - op => Assert.Equal("AfterUpdaterAsync:Updater2", op), - - op => Assert.Equal("state.Notify", op) - ); - } - } - - - public record TestAction : IAction; - public record TestState : StateBase; -} From 8d523713318f0a0c0e860eaf92431d63736a6eb1 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 19:39:19 -0500 Subject: [PATCH 04/17] Register IState --- src/StateR/IStatorBuilder.cs | 19 +++-- src/StateR/Internal/StatorBuilder.cs | 59 ++++++++++++- src/StateR/Pipeline/IActionFilterFactory.cs | 1 - src/StateR/StatorStartupExtensions.cs | 22 +++++ .../Internal/StatorBuilderTest.cs | 82 +++++++++++++++++++ .../StatorStartupExtensionsTest.cs | 41 +++++++++- test/StateR.Tests/TestTypes.cs | 20 +++++ 7 files changed, 232 insertions(+), 12 deletions(-) create mode 100644 test/StateR.Tests/TestTypes.cs diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index 3c1b3b3..426cf89 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -1,12 +1,23 @@ using Microsoft.Extensions.DependencyInjection; +using System.Collections.ObjectModel; namespace StateR; -public interface IStatorBuilder +public interface IStatorBuilder : IOldStatorBuilder { IServiceCollection Services { get; } + ReadOnlyCollection States { get; } + ReadOnlyCollection InitialStates { get; } + + IStatorBuilder AddState() + where TState : StateBase + where TInitialState : IInitialState; + IStatorBuilder AddState(Type state, Type initialState); +} + +public interface IOldStatorBuilder +{ List Actions { get; } - List States { get; } //List Interceptors { get; } List ActionHandlers { get; } //List AfterEffects { get; } @@ -22,10 +33,6 @@ public interface IStatorBuilder IStatorBuilder AddMiddlewares(IEnumerable types); List Middlewares { get; } - - IStatorBuilder AddState() - where TState : StateBase; - //IStatorBuilder AddAction() // where TState : StateBase // where TAction : IAction; diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index 4898d31..d1b3fef 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -1,18 +1,25 @@ using Microsoft.Extensions.DependencyInjection; +using System.Collections.ObjectModel; namespace StateR.Internal; public class StatorBuilder : IStatorBuilder { + private readonly List _states = new(); + private readonly List _initialStates = new(); + public StatorBuilder(IServiceCollection services) { Services = services ?? throw new ArgumentNullException(nameof(services)); } + + #region IOldStatorBuilder + public IStatorBuilder AddTypes(IEnumerable types) => AddDistinctTypes(All, types); public IStatorBuilder AddStates(IEnumerable types) - => AddDistinctTypes(States, types); + => AddDistinctTypes(_states, types); public IStatorBuilder AddActions(IEnumerable types) => AddDistinctTypes(Actions, types); public IStatorBuilder AddUpdaters(IEnumerable types) @@ -22,7 +29,6 @@ public IStatorBuilder AddActionHandlers(IEnumerable types) public IServiceCollection Services { get; } public List Actions { get; } = new List(); - public List States { get; } = new List(); public List Interceptors { get; } = new List(); public List ActionHandlers { get; } = new List(); public List AfterEffects { get; } = new List(); @@ -40,9 +46,54 @@ private IStatorBuilder AddDistinctTypes(List list, IEnumerable types return this; } - public IStatorBuilder AddState() where TState : StateBase + #endregion + + public ReadOnlyCollection States => new(_states); + public ReadOnlyCollection InitialStates => new(_initialStates); + + public IStatorBuilder AddState() + where TState : StateBase + where TInitialState : IInitialState { - States.Add(typeof(TState)); + _states.Add(typeof(TState)); + _initialStates.Add(typeof(TInitialState)); return this; } + + public IStatorBuilder AddState(Type state, Type initialState) + { + if (!state.IsAssignableTo(typeof(StateBase))) + { + throw new InvalidStateException(state); + } + if (!initialState.IsAssignableTo(typeof(IInitialState<>).MakeGenericType(state))) + { + throw new InvalidInitialStateException(state, initialState); + } + _states.Add(state); + return this; + } +} + +public class InvalidStateException : Exception +{ + public InvalidStateException(Type stateType) + { + StateType = stateType; + } + + public Type StateType { get; } +} + +public class InvalidInitialStateException : Exception +{ + public InvalidInitialStateException(Type stateType, Type initialState) + : base($"The type {initialState.Name} is not a valid IInitialState<{stateType.Name}>.") + { + StateType = stateType; + InitialStateType = initialState; + } + + public Type StateType { get; } + public Type InitialStateType { get; } } diff --git a/src/StateR/Pipeline/IActionFilterFactory.cs b/src/StateR/Pipeline/IActionFilterFactory.cs index 10cb79a..4e17058 100644 --- a/src/StateR/Pipeline/IActionFilterFactory.cs +++ b/src/StateR/Pipeline/IActionFilterFactory.cs @@ -22,5 +22,4 @@ public IActionFilter Create(IDispatchContext where TState : StateBase => _serviceProvider.GetRequiredService>(); - } \ No newline at end of file diff --git a/src/StateR/StatorStartupExtensions.cs b/src/StateR/StatorStartupExtensions.cs index cac8df4..feb8aca 100644 --- a/src/StateR/StatorStartupExtensions.cs +++ b/src/StateR/StatorStartupExtensions.cs @@ -48,6 +48,28 @@ public static IStatorBuilder AddStateR(this IServiceCollection services, params public static IServiceCollection Apply(this IStatorBuilder builder, Action? postConfiguration = null) { + // Register States + foreach (var state in builder.States) + { + Console.WriteLine($"state: {state.FullName}"); + + // Equivalent to: AddSingleton, State>(); + var stateServiceType = typeof(IState<>).MakeGenericType(state); + var stateImplementationType = typeof(State<>).MakeGenericType(state); + builder.Services.AddSingleton(stateServiceType, stateImplementationType); + } + + // Register Initial States + builder.Services.Scan(s => s + .AddTypes(builder.InitialStates) + + // Equivalent to: AddSingleton, Implementation>(); + .AddClasses(classes => classes.AssignableTo(typeof(IInitialState<>))) + .AsImplementedInterfaces() + .WithSingletonLifetime() + ); + + return builder.Services; // Extract types diff --git a/test/StateR.Tests/Internal/StatorBuilderTest.cs b/test/StateR.Tests/Internal/StatorBuilderTest.cs index 208f67b..5eb581b 100644 --- a/test/StateR.Tests/Internal/StatorBuilderTest.cs +++ b/test/StateR.Tests/Internal/StatorBuilderTest.cs @@ -5,6 +5,88 @@ namespace StateR.Internal; public class StatorBuilderTest { + public class AddState_TState : StatorBuilderTest + { + [Fact] + public void Should_add_TState_to_States_and_TInitialState_to_InitialStates() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + + // Act + sut.AddState(); + + // Assert + Assert.Collection(sut.States, + type => Assert.Equal(typeof(TestState1), type) + ); + Assert.Collection(sut.InitialStates, + type => Assert.Equal(typeof(InitialTestState1), type) + ); + } + } + public class AddState_Type : StatorBuilderTest + { + [Fact] + public void Should_add_a_valid_state_type_to_States() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + + // Act + sut.AddState(typeof(TestState1), typeof(InitialTestState1)); + + // Assert + Assert.Collection(sut.States, + type => Assert.Equal(typeof(TestState1), type) + ); + } + + [Fact] + public void Should_throw_an_InvalidStateException_when_the_state_type_does_not_inherit_StateBase() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + var stateType = typeof(NotAState); + var initialStateType = typeof(InitialTestState1); + + // Act & Assert + var ex = Assert.Throws(() => sut.AddState(stateType, initialStateType)); + Assert.Same(stateType, ex.StateType); + } + + [Fact] + public void Should_throw_an_InvalidInitialStateException_when_the_initialState_does_not_implement_IInitialState() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + var stateType = typeof(TestState1); + var initialStateType = typeof(NotAState); + + // Act & Assert + var ex = Assert.Throws(() => sut.AddState(stateType, initialStateType)); + Assert.Same(initialStateType, ex.InitialStateType); + } + + [Fact] + public void Should_throw_an_InvalidInitialStateException_when_the_initialState_does_not_initialize_state() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + var stateType = typeof(TestState1); + var initialStateType = typeof(InitialTestState2); + + // Act & Assert + var ex = Assert.Throws(() => sut.AddState(stateType, initialStateType)); + Assert.Same(initialStateType, ex.InitialStateType); + } + } + public class AddTypes : StatorBuilderTest { [Fact] diff --git a/test/StateR.Tests/StatorStartupExtensionsTest.cs b/test/StateR.Tests/StatorStartupExtensionsTest.cs index 6e042f6..10fed5d 100644 --- a/test/StateR.Tests/StatorStartupExtensionsTest.cs +++ b/test/StateR.Tests/StatorStartupExtensionsTest.cs @@ -1,9 +1,48 @@ -namespace StateR; +using Microsoft.Extensions.DependencyInjection; +using StateR.Internal; +using System; +using Xunit; +namespace StateR; public class StatorStartupExtensionsTest { public class AddStateR : StatorStartupExtensionsTest { + [Fact(Skip = "TODO: implement tests")] + public void Should_be_tested() + { + // Arrange + + // Act + + + // Assert + throw new NotImplementedException(); + } + } + + public class Apply : StatorStartupExtensionsTest + { + [Fact] + public void Should_add_IState_to_the_ServiceCollection() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services) + .AddState() + .AddState() + .AddState() + ; + + // Act + sut.Apply(); + + // Assert + var sp = services.BuildServiceProvider(); + sp.GetRequiredService>(); + sp.GetRequiredService>(); + sp.GetRequiredService>(); + } } } diff --git a/test/StateR.Tests/TestTypes.cs b/test/StateR.Tests/TestTypes.cs new file mode 100644 index 0000000..45c9e8b --- /dev/null +++ b/test/StateR.Tests/TestTypes.cs @@ -0,0 +1,20 @@ +namespace StateR; + +public record class TestState1 : StateBase; +public record class TestState2 : StateBase; +public record class TestState3 : StateBase; + +public record class InitialTestState1 : IInitialState +{ + public TestState1 Value => new(); +} +public record class InitialTestState2 : IInitialState +{ + public TestState2 Value => new(); +} +public record class InitialTestState3 : IInitialState +{ + public TestState3 Value => new(); +} + +public class NotAState { } From 778eeba04151a701dc6ba0f443f3685a5c5b88f3 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 19:42:05 -0500 Subject: [PATCH 05/17] Make Store internal --- src/StateR/{ => Internal}/Store.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/StateR/{ => Internal}/Store.cs (98%) diff --git a/src/StateR/Store.cs b/src/StateR/Internal/Store.cs similarity index 98% rename from src/StateR/Store.cs rename to src/StateR/Internal/Store.cs index ad627df..2a8d67a 100644 --- a/src/StateR/Store.cs +++ b/src/StateR/Internal/Store.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.DependencyInjection; -namespace StateR; +namespace StateR.Internal; public class Store : IStore { From 9b13046dbb8c9f8137fb50b6d239a6f8c9b0d46f Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 20:16:51 -0500 Subject: [PATCH 06/17] Cleanup --- src/StateR/Internal/TypeScannerBuilderExtensions.cs | 1 + src/StateR/Pipeline/IActionFilterFactory.cs | 1 - src/StateR/StatorStartupExtensions.cs | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/StateR/Internal/TypeScannerBuilderExtensions.cs b/src/StateR/Internal/TypeScannerBuilderExtensions.cs index 628dbd0..1365f31 100644 --- a/src/StateR/Internal/TypeScannerBuilderExtensions.cs +++ b/src/StateR/Internal/TypeScannerBuilderExtensions.cs @@ -53,6 +53,7 @@ public static IEnumerable FindUpdaters(IEnumerable types) ); return updaters; } + public static IEnumerable FindMiddlewares(IEnumerable types) { var handlers = types diff --git a/src/StateR/Pipeline/IActionFilterFactory.cs b/src/StateR/Pipeline/IActionFilterFactory.cs index 4e17058..19da883 100644 --- a/src/StateR/Pipeline/IActionFilterFactory.cs +++ b/src/StateR/Pipeline/IActionFilterFactory.cs @@ -1,6 +1,5 @@  using Microsoft.Extensions.DependencyInjection; -using System; namespace StateR.Pipeline; public interface IActionFilterFactory diff --git a/src/StateR/StatorStartupExtensions.cs b/src/StateR/StatorStartupExtensions.cs index feb8aca..2b51bca 100644 --- a/src/StateR/StatorStartupExtensions.cs +++ b/src/StateR/StatorStartupExtensions.cs @@ -153,7 +153,7 @@ public static IServiceCollection Apply(this IStatorBuilder builder, Action i.IsGenericType && i.GetGenericTypeDefinition() == iUpdaterType); foreach (var @interface in interfaces) { - // Equivalent to: AddSingleton, UpdaterMiddleware> + // Equivalent to: AddSingleton, UpdaterMiddleware> var actionType = @interface.GenericTypeArguments[0]; var stateType = @interface.GenericTypeArguments[1]; var iMiddlewareServiceType = handlerType.MakeGenericType(actionType, stateType); From 17a7be7b0d8d59e5d603063a7346f4e1bba8187e Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 20:17:22 -0500 Subject: [PATCH 07/17] Add actions to StatorBuilder --- src/StateR/IStatorBuilder.cs | 7 ++- src/StateR/Internal/StatorBuilder.cs | 42 +++++++++++++- .../Internal/StatorBuilderTest.cs | 58 ++++++++++++++++--- test/StateR.Tests/TestTypes.cs | 5 ++ 4 files changed, 100 insertions(+), 12 deletions(-) diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index 426cf89..a7c51bd 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -8,16 +8,21 @@ public interface IStatorBuilder : IOldStatorBuilder IServiceCollection Services { get; } ReadOnlyCollection States { get; } ReadOnlyCollection InitialStates { get; } + ReadOnlyCollection Actions { get; } IStatorBuilder AddState() where TState : StateBase where TInitialState : IInitialState; IStatorBuilder AddState(Type state, Type initialState); + + IStatorBuilder AddAction() + where TAction : IAction + where TState : StateBase; + IStatorBuilder AddAction(Type actionType); } public interface IOldStatorBuilder { - List Actions { get; } //List Interceptors { get; } List ActionHandlers { get; } //List AfterEffects { get; } diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index d1b3fef..6dd02bb 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -7,6 +7,7 @@ public class StatorBuilder : IStatorBuilder { private readonly List _states = new(); private readonly List _initialStates = new(); + private readonly List _actions = new(); public StatorBuilder(IServiceCollection services) { @@ -21,14 +22,13 @@ public IStatorBuilder AddTypes(IEnumerable types) public IStatorBuilder AddStates(IEnumerable types) => AddDistinctTypes(_states, types); public IStatorBuilder AddActions(IEnumerable types) - => AddDistinctTypes(Actions, types); + => AddDistinctTypes(_actions, types); public IStatorBuilder AddUpdaters(IEnumerable types) => AddDistinctTypes(Updaters, types); public IStatorBuilder AddActionHandlers(IEnumerable types) => AddDistinctTypes(ActionHandlers, types); public IServiceCollection Services { get; } - public List Actions { get; } = new List(); public List Interceptors { get; } = new List(); public List ActionHandlers { get; } = new List(); public List AfterEffects { get; } = new List(); @@ -50,6 +50,7 @@ private IStatorBuilder AddDistinctTypes(List list, IEnumerable types public ReadOnlyCollection States => new(_states); public ReadOnlyCollection InitialStates => new(_initialStates); + public ReadOnlyCollection Actions => new(_actions); public IStatorBuilder AddState() where TState : StateBase @@ -73,6 +74,32 @@ public IStatorBuilder AddState(Type state, Type initialState) _states.Add(state); return this; } + + public IStatorBuilder AddAction() + where TAction : IAction + where TState : StateBase + { + _actions.Add(typeof(TAction)); + return this; + } + + public IStatorBuilder AddAction(Type actionType) + { + if(!IsAction(actionType)) + { + throw new InvalidActionException(actionType); + } + _actions.Add(actionType); + return this; + } + + private static readonly Type _iActionType = typeof(IAction<>); + private static bool IsAction(Type actionType) + { + var interfaces = actionType.GetInterfaces() + .Count(i => i.IsGenericType && i.GetGenericTypeDefinition() == _iActionType); + return interfaces > 0; + } } public class InvalidStateException : Exception @@ -97,3 +124,14 @@ public InvalidInitialStateException(Type stateType, Type initialState) public Type StateType { get; } public Type InitialStateType { get; } } + +public class InvalidActionException : Exception +{ + public InvalidActionException(Type actionType) + : base($"The type {actionType.Name} is not a valid IAction.") + { + ActionType = actionType; + } + + public Type ActionType { get; } +} \ No newline at end of file diff --git a/test/StateR.Tests/Internal/StatorBuilderTest.cs b/test/StateR.Tests/Internal/StatorBuilderTest.cs index 5eb581b..f503298 100644 --- a/test/StateR.Tests/Internal/StatorBuilderTest.cs +++ b/test/StateR.Tests/Internal/StatorBuilderTest.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using System; using Xunit; namespace StateR.Internal; @@ -58,35 +59,74 @@ public void Should_throw_an_InvalidStateException_when_the_state_type_does_not_i Assert.Same(stateType, ex.StateType); } - [Fact] - public void Should_throw_an_InvalidInitialStateException_when_the_initialState_does_not_implement_IInitialState() + [Theory] + [InlineData(typeof(TestState1), typeof(InitialTestState2))] + [InlineData(typeof(TestState1), typeof(NotAState))] + public void Should_throw_an_InvalidInitialStateException_when_the_initialState_is_invalid(Type stateType, Type initialStateType) { // Arrange var services = new ServiceCollection(); var sut = new StatorBuilder(services); - var stateType = typeof(TestState1); - var initialStateType = typeof(NotAState); // Act & Assert var ex = Assert.Throws(() => sut.AddState(stateType, initialStateType)); Assert.Same(initialStateType, ex.InitialStateType); } + } + public class AddAction_TAction_TState + { [Fact] - public void Should_throw_an_InvalidInitialStateException_when_the_initialState_does_not_initialize_state() + public void Should_add_TAction_to_Actions() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + + // Act + sut.AddAction(); + + // Assert + Assert.Collection(sut.Actions, + type => Assert.Equal(typeof(TestAction1), type) + ); + } + } + + public class AddAction_Type + { + [Fact] + public void Should_add_TAction_to_Actions() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + var actionType = typeof(TestAction1); + + // Act + sut.AddAction(actionType); + + // Assert + Assert.Collection(sut.Actions, + type => Assert.Same(actionType, type) + ); + } + + [Theory] + [InlineData(typeof(NotAnAction))] + public void Should_throw_an_InvalidActionException_when_actionType_is_invalid(Type actionType) { // Arrange var services = new ServiceCollection(); var sut = new StatorBuilder(services); - var stateType = typeof(TestState1); - var initialStateType = typeof(InitialTestState2); // Act & Assert - var ex = Assert.Throws(() => sut.AddState(stateType, initialStateType)); - Assert.Same(initialStateType, ex.InitialStateType); + var ex = Assert.Throws(() => sut.AddAction(actionType)); + Assert.Same(actionType, ex.ActionType); } } + public class AddTypes : StatorBuilderTest { [Fact] diff --git a/test/StateR.Tests/TestTypes.cs b/test/StateR.Tests/TestTypes.cs index 45c9e8b..ff81e41 100644 --- a/test/StateR.Tests/TestTypes.cs +++ b/test/StateR.Tests/TestTypes.cs @@ -18,3 +18,8 @@ public record class InitialTestState3 : IInitialState } public class NotAState { } +public class NotAnAction { } + +public record TestAction1 : IAction; +public record TestAction2 : IAction; +public record TestAction3 : IAction; From b59e0134ddc037672a36ce785d4e93b04e45e8c3 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 20:45:23 -0500 Subject: [PATCH 08/17] Register updaters --- src/StateR/IStatorBuilder.cs | 9 ++- src/StateR/Internal/StatorBuilder.cs | 47 +++++++++++- src/StateR/StatorStartupExtensions.cs | 76 +++++++++++++------ .../Internal/StatorBuilderTest.cs | 56 ++++++++++++++ .../StatorStartupExtensionsTest.cs | 22 ++++++ test/StateR.Tests/TestTypes.cs | 11 ++- 6 files changed, 191 insertions(+), 30 deletions(-) diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index a7c51bd..8dadfa7 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using StateR.Updaters; using System.Collections.ObjectModel; namespace StateR; @@ -9,6 +10,7 @@ public interface IStatorBuilder : IOldStatorBuilder ReadOnlyCollection States { get; } ReadOnlyCollection InitialStates { get; } ReadOnlyCollection Actions { get; } + ReadOnlyCollection Updaters { get; } IStatorBuilder AddState() where TState : StateBase @@ -19,6 +21,12 @@ IStatorBuilder AddAction() where TAction : IAction where TState : StateBase; IStatorBuilder AddAction(Type actionType); + + IStatorBuilder AddUpdater() + where TUpdater : IUpdater + where TAction : IAction + where TState : StateBase; + IStatorBuilder AddUpdater(Type updaterType); } public interface IOldStatorBuilder @@ -26,7 +34,6 @@ public interface IOldStatorBuilder //List Interceptors { get; } List ActionHandlers { get; } //List AfterEffects { get; } - List Updaters { get; } List All { get; } IStatorBuilder AddTypes(IEnumerable types); diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index 6dd02bb..0638d98 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using StateR.Updaters; using System.Collections.ObjectModel; namespace StateR.Internal; @@ -8,6 +9,7 @@ public class StatorBuilder : IStatorBuilder private readonly List _states = new(); private readonly List _initialStates = new(); private readonly List _actions = new(); + private readonly List _updaters = new(); public StatorBuilder(IServiceCollection services) { @@ -24,7 +26,7 @@ public IStatorBuilder AddStates(IEnumerable types) public IStatorBuilder AddActions(IEnumerable types) => AddDistinctTypes(_actions, types); public IStatorBuilder AddUpdaters(IEnumerable types) - => AddDistinctTypes(Updaters, types); + => AddDistinctTypes(_updaters, types); public IStatorBuilder AddActionHandlers(IEnumerable types) => AddDistinctTypes(ActionHandlers, types); @@ -32,7 +34,6 @@ public IStatorBuilder AddActionHandlers(IEnumerable types) public List Interceptors { get; } = new List(); public List ActionHandlers { get; } = new List(); public List AfterEffects { get; } = new List(); - public List Updaters { get; } = new List(); public List All { get; } = new List(); public IStatorBuilder AddMiddlewares(IEnumerable types) @@ -51,6 +52,7 @@ private IStatorBuilder AddDistinctTypes(List list, IEnumerable types public ReadOnlyCollection States => new(_states); public ReadOnlyCollection InitialStates => new(_initialStates); public ReadOnlyCollection Actions => new(_actions); + public ReadOnlyCollection Updaters => new(_updaters); public IStatorBuilder AddState() where TState : StateBase @@ -93,6 +95,25 @@ public IStatorBuilder AddAction(Type actionType) return this; } + public IStatorBuilder AddUpdater() + where TUpdater : IUpdater + where TAction : IAction + where TState : StateBase + { + _updaters.Add(typeof(TUpdater)); + return this; + } + + public IStatorBuilder AddUpdater(Type updaterType) + { + if(!IsUpdater(updaterType)) + { + throw new InvalidUpdaterException(updaterType); + } + _updaters.Add(updaterType); + return this; + } + private static readonly Type _iActionType = typeof(IAction<>); private static bool IsAction(Type actionType) { @@ -100,6 +121,15 @@ private static bool IsAction(Type actionType) .Count(i => i.IsGenericType && i.GetGenericTypeDefinition() == _iActionType); return interfaces > 0; } + + private static readonly Type _iUpdaterType = typeof(IUpdater<,>); + private static bool IsUpdater(Type updaterType) + { + var interfaces = updaterType.GetInterfaces() + .Count(i => i.IsGenericType && i.GetGenericTypeDefinition() == _iUpdaterType); + return interfaces > 0; + } + } public class InvalidStateException : Exception @@ -128,10 +158,21 @@ public InvalidInitialStateException(Type stateType, Type initialState) public class InvalidActionException : Exception { public InvalidActionException(Type actionType) - : base($"The type {actionType.Name} is not a valid IAction.") + : base($"The type {actionType.Name} is not a valid IAction.") { ActionType = actionType; } public Type ActionType { get; } +} + +public class InvalidUpdaterException : Exception +{ + public InvalidUpdaterException(Type updaterType) + : base($"The type {updaterType.Name} is not a valid IUpdater.") + { + UpdaterType = updaterType; + } + + public Type UpdaterType { get; } } \ No newline at end of file diff --git a/src/StateR/StatorStartupExtensions.cs b/src/StateR/StatorStartupExtensions.cs index 2b51bca..5df5292 100644 --- a/src/StateR/StatorStartupExtensions.cs +++ b/src/StateR/StatorStartupExtensions.cs @@ -69,6 +69,32 @@ public static IServiceCollection Apply(this IStatorBuilder builder, Action); + var updaterHandler = typeof(UpdaterMiddleware<,>); + var handlerType = typeof(IActionFilter<,>); + foreach (var updater in builder.Updaters) + { + Console.WriteLine($"updater: {updater.FullName}"); + var interfaces = updater.GetInterfaces() + .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == iUpdaterType); + foreach (var @interface in interfaces) + { + // Equivalent to: AddSingleton, UpdaterMiddleware> + var actionType = @interface.GenericTypeArguments[0]; + var stateType = @interface.GenericTypeArguments[1]; + var iMiddlewareServiceType = handlerType.MakeGenericType(actionType, stateType); + var updaterMiddlewareImplementationType = updaterHandler.MakeGenericType(stateType, actionType); + builder.Services.AddSingleton(iMiddlewareServiceType, updaterMiddlewareImplementationType); + + // Equivalent to: AddSingleton, Updater>(); + builder.Services.AddSingleton(@interface, updater); + + Console.WriteLine($"- AddSingleton<{iMiddlewareServiceType.GetStatorName()}, {updaterMiddlewareImplementationType.GetStatorName()}>()"); + Console.WriteLine($"- AddSingleton<{@interface.GetStatorName()}, {updater.GetStatorName()}>()"); + } + } + return builder.Services; @@ -142,31 +168,31 @@ public static IServiceCollection Apply(this IStatorBuilder builder, Action); - var updaterHandler = typeof(UpdaterMiddleware<,>); - var handlerType = typeof(IActionFilter<,>); - foreach (var updater in builder.Updaters) - { - Console.WriteLine($"updater: {updater.FullName}"); - var interfaces = updater.GetInterfaces() - .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == iUpdaterType); - foreach (var @interface in interfaces) - { - // Equivalent to: AddSingleton, UpdaterMiddleware> - var actionType = @interface.GenericTypeArguments[0]; - var stateType = @interface.GenericTypeArguments[1]; - var iMiddlewareServiceType = handlerType.MakeGenericType(actionType, stateType); - var updaterMiddlewareImplementationType = updaterHandler.MakeGenericType(stateType, actionType); - builder.Services.AddSingleton(iMiddlewareServiceType, updaterMiddlewareImplementationType); - - // Equivalent to: AddSingleton, Updater>(); - builder.Services.AddSingleton(@interface, updater); - - Console.WriteLine($"- AddSingleton<{iMiddlewareServiceType.GetStatorName()}, {updaterMiddlewareImplementationType.GetStatorName()}>()"); - Console.WriteLine($"- AddSingleton<{@interface.GetStatorName()}, {updater.GetStatorName()}>()"); - } - } + //// Register Updaters and their respective IMiddleware + //var iUpdaterType = typeof(IUpdater<,>); + //var updaterHandler = typeof(UpdaterMiddleware<,>); + //var handlerType = typeof(IActionFilter<,>); + //foreach (var updater in builder.Updaters) + //{ + // Console.WriteLine($"updater: {updater.FullName}"); + // var interfaces = updater.GetInterfaces() + // .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == iUpdaterType); + // foreach (var @interface in interfaces) + // { + // // Equivalent to: AddSingleton, UpdaterMiddleware> + // var actionType = @interface.GenericTypeArguments[0]; + // var stateType = @interface.GenericTypeArguments[1]; + // var iMiddlewareServiceType = handlerType.MakeGenericType(actionType, stateType); + // var updaterMiddlewareImplementationType = updaterHandler.MakeGenericType(stateType, actionType); + // builder.Services.AddSingleton(iMiddlewareServiceType, updaterMiddlewareImplementationType); + + // // Equivalent to: AddSingleton, Updater>(); + // builder.Services.AddSingleton(@interface, updater); + + // Console.WriteLine($"- AddSingleton<{iMiddlewareServiceType.GetStatorName()}, {updaterMiddlewareImplementationType.GetStatorName()}>()"); + // Console.WriteLine($"- AddSingleton<{@interface.GetStatorName()}, {updater.GetStatorName()}>()"); + // } + //} // Register Middleware foreach (var middleware in builder.Middlewares.AsEnumerable().Reverse()) diff --git a/test/StateR.Tests/Internal/StatorBuilderTest.cs b/test/StateR.Tests/Internal/StatorBuilderTest.cs index f503298..010a904 100644 --- a/test/StateR.Tests/Internal/StatorBuilderTest.cs +++ b/test/StateR.Tests/Internal/StatorBuilderTest.cs @@ -126,6 +126,62 @@ public void Should_throw_an_InvalidActionException_when_actionType_is_invalid(Ty } } + public class AddUpdater_TUpdater_TAction_TState + { + [Fact] + public void Should_add_TUpdater_to_Updaters() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + + // Act + sut.AddUpdater(); + + // Assert + Assert.Collection(sut.Updaters, + type => Assert.Equal(typeof(TestUpdaters), type) + ); + } + } + + public class AddUpdater_Type + { + [Fact] + public void Should_add_updaterType_to_Updaters() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + var updaterType = typeof(TestUpdaters); + + // Act + sut.AddUpdater(updaterType); + + // Assert + Assert.Collection(sut.Updaters, + type => Assert.Same(updaterType, type) + ); + } + + [Theory] + [InlineData(typeof(NotAnUpdater))] + public void Should_throw_an_InvalidUpdaterException_when_updaterType_is_invalid(Type updaterType) + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + + // Act & Assert + var ex = Assert.Throws(() => sut.AddUpdater(updaterType)); + Assert.Same(updaterType, ex.UpdaterType); + } + + //where TUpdater : IUpdater + //where TAction : IAction + //where TState : StateBase + + } public class AddTypes : StatorBuilderTest { diff --git a/test/StateR.Tests/StatorStartupExtensionsTest.cs b/test/StateR.Tests/StatorStartupExtensionsTest.cs index 10fed5d..8ac6e9e 100644 --- a/test/StateR.Tests/StatorStartupExtensionsTest.cs +++ b/test/StateR.Tests/StatorStartupExtensionsTest.cs @@ -1,5 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using StateR.Internal; +using StateR.Pipeline; +using StateR.Updaters; using System; using Xunit; namespace StateR; @@ -44,5 +46,25 @@ public void Should_add_IState_to_the_ServiceCollection() sp.GetRequiredService>(); sp.GetRequiredService>(); } + + [Fact] + public void Should_add_IUpdater_and_IActionFilter_to_the_ServiceCollection() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services) + .AddState() + .AddAction() + .AddUpdater() + ; + + // Act + sut.Apply(); + + // Assert + var sp = services.BuildServiceProvider(); + sp.GetRequiredService>(); + sp.GetRequiredService>(); + } } } diff --git a/test/StateR.Tests/TestTypes.cs b/test/StateR.Tests/TestTypes.cs index ff81e41..46100df 100644 --- a/test/StateR.Tests/TestTypes.cs +++ b/test/StateR.Tests/TestTypes.cs @@ -1,4 +1,6 @@ -namespace StateR; +using StateR.Updaters; + +namespace StateR; public record class TestState1 : StateBase; public record class TestState2 : StateBase; @@ -19,7 +21,14 @@ public record class InitialTestState3 : IInitialState public class NotAState { } public class NotAnAction { } +public class NotAnUpdater { } public record TestAction1 : IAction; public record TestAction2 : IAction; public record TestAction3 : IAction; + +public class TestUpdaters : IUpdater +{ + public TestState1 Update(TestAction1 action, TestState1 state) + => new(); +} \ No newline at end of file From ce5710303338f3c2a912653994d4368f49ee1e08 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 21:20:26 -0500 Subject: [PATCH 09/17] Remove generic AddAction and AddUpdater methods --- src/StateR/IStatorBuilder.cs | 16 ++-- src/StateR/Internal/StatorBuilder.cs | 32 ++++---- .../Internal/StatorBuilderTest.cs | 80 +++++++++---------- .../StatorStartupExtensionsTest.cs | 4 +- test/StateR.Tests/TestTypes.cs | 2 - 5 files changed, 66 insertions(+), 68 deletions(-) diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index 8dadfa7..cd31de8 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -17,16 +17,16 @@ IStatorBuilder AddState() where TInitialState : IInitialState; IStatorBuilder AddState(Type state, Type initialState); - IStatorBuilder AddAction() - where TAction : IAction - where TState : StateBase; + //IStatorBuilder AddAction() + // where TAction : IAction + // where TState : StateBase; IStatorBuilder AddAction(Type actionType); - IStatorBuilder AddUpdater() - where TUpdater : IUpdater - where TAction : IAction - where TState : StateBase; - IStatorBuilder AddUpdater(Type updaterType); + //IStatorBuilder AddUpdater() + // where TUpdater : IUpdater + // where TAction : IAction + // where TState : StateBase; + IStatorBuilder AddUpdaters(Type updaterType); } public interface IOldStatorBuilder diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index 0638d98..ef47bbc 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -77,13 +77,13 @@ public IStatorBuilder AddState(Type state, Type initialState) return this; } - public IStatorBuilder AddAction() - where TAction : IAction - where TState : StateBase - { - _actions.Add(typeof(TAction)); - return this; - } + //public IStatorBuilder AddAction() + // where TAction : IAction + // where TState : StateBase + //{ + // _actions.Add(typeof(TAction)); + // return this; + //} public IStatorBuilder AddAction(Type actionType) { @@ -95,16 +95,16 @@ public IStatorBuilder AddAction(Type actionType) return this; } - public IStatorBuilder AddUpdater() - where TUpdater : IUpdater - where TAction : IAction - where TState : StateBase - { - _updaters.Add(typeof(TUpdater)); - return this; - } + //public IStatorBuilder AddUpdater() + // where TUpdater : IUpdater + // where TAction : IAction + // where TState : StateBase + //{ + // _updaters.Add(typeof(TUpdater)); + // return this; + //} - public IStatorBuilder AddUpdater(Type updaterType) + public IStatorBuilder AddUpdaters(Type updaterType) { if(!IsUpdater(updaterType)) { diff --git a/test/StateR.Tests/Internal/StatorBuilderTest.cs b/test/StateR.Tests/Internal/StatorBuilderTest.cs index 010a904..51b6560 100644 --- a/test/StateR.Tests/Internal/StatorBuilderTest.cs +++ b/test/StateR.Tests/Internal/StatorBuilderTest.cs @@ -74,24 +74,24 @@ public void Should_throw_an_InvalidInitialStateException_when_the_initialState_i } } - public class AddAction_TAction_TState - { - [Fact] - public void Should_add_TAction_to_Actions() - { - // Arrange - var services = new ServiceCollection(); - var sut = new StatorBuilder(services); - - // Act - sut.AddAction(); - - // Assert - Assert.Collection(sut.Actions, - type => Assert.Equal(typeof(TestAction1), type) - ); - } - } + //public class AddAction_TAction_TState + //{ + // [Fact] + // public void Should_add_TAction_to_Actions() + // { + // // Arrange + // var services = new ServiceCollection(); + // var sut = new StatorBuilder(services); + + // // Act + // sut.AddAction(); + + // // Assert + // Assert.Collection(sut.Actions, + // type => Assert.Equal(typeof(TestAction1), type) + // ); + // } + //} public class AddAction_Type { @@ -126,26 +126,26 @@ public void Should_throw_an_InvalidActionException_when_actionType_is_invalid(Ty } } - public class AddUpdater_TUpdater_TAction_TState - { - [Fact] - public void Should_add_TUpdater_to_Updaters() - { - // Arrange - var services = new ServiceCollection(); - var sut = new StatorBuilder(services); - - // Act - sut.AddUpdater(); - - // Assert - Assert.Collection(sut.Updaters, - type => Assert.Equal(typeof(TestUpdaters), type) - ); - } - } - - public class AddUpdater_Type + //public class AddUpdater_TUpdater_TAction_TState + //{ + // [Fact] + // public void Should_add_TUpdater_to_Updaters() + // { + // // Arrange + // var services = new ServiceCollection(); + // var sut = new StatorBuilder(services); + + // // Act + // sut.AddUpdater(); + + // // Assert + // Assert.Collection(sut.Updaters, + // type => Assert.Equal(typeof(TestUpdaters), type) + // ); + // } + //} + + public class AddUpdaters_Type { [Fact] public void Should_add_updaterType_to_Updaters() @@ -156,7 +156,7 @@ public void Should_add_updaterType_to_Updaters() var updaterType = typeof(TestUpdaters); // Act - sut.AddUpdater(updaterType); + sut.AddUpdaters(updaterType); // Assert Assert.Collection(sut.Updaters, @@ -173,7 +173,7 @@ public void Should_throw_an_InvalidUpdaterException_when_updaterType_is_invalid( var sut = new StatorBuilder(services); // Act & Assert - var ex = Assert.Throws(() => sut.AddUpdater(updaterType)); + var ex = Assert.Throws(() => sut.AddUpdaters(updaterType)); Assert.Same(updaterType, ex.UpdaterType); } diff --git a/test/StateR.Tests/StatorStartupExtensionsTest.cs b/test/StateR.Tests/StatorStartupExtensionsTest.cs index 8ac6e9e..7c08996 100644 --- a/test/StateR.Tests/StatorStartupExtensionsTest.cs +++ b/test/StateR.Tests/StatorStartupExtensionsTest.cs @@ -54,8 +54,8 @@ public void Should_add_IUpdater_and_IActionFilter_to_the_ServiceCollection() var services = new ServiceCollection(); var sut = new StatorBuilder(services) .AddState() - .AddAction() - .AddUpdater() + .AddAction(typeof(TestAction1)) + .AddUpdaters(typeof(TestUpdaters)) ; // Act diff --git a/test/StateR.Tests/TestTypes.cs b/test/StateR.Tests/TestTypes.cs index 46100df..df2ecb1 100644 --- a/test/StateR.Tests/TestTypes.cs +++ b/test/StateR.Tests/TestTypes.cs @@ -24,8 +24,6 @@ public class NotAnAction { } public class NotAnUpdater { } public record TestAction1 : IAction; -public record TestAction2 : IAction; -public record TestAction3 : IAction; public class TestUpdaters : IUpdater { From 12e256904fc053f8db291a3ff6bc97c36c12fc6d Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 21:20:40 -0500 Subject: [PATCH 10/17] Add a counter integration test --- test/StateR.Tests/IntegrationTest.cs | 84 +++++++++++++++++++++++++++ test/StateR.Tests/StateR.Tests.csproj | 1 + 2 files changed, 85 insertions(+) create mode 100644 test/StateR.Tests/IntegrationTest.cs diff --git a/test/StateR.Tests/IntegrationTest.cs b/test/StateR.Tests/IntegrationTest.cs new file mode 100644 index 0000000..82acb87 --- /dev/null +++ b/test/StateR.Tests/IntegrationTest.cs @@ -0,0 +1,84 @@ +using Microsoft.Extensions.DependencyInjection; +using StateR.Internal; +using StateR.Updaters; +using System; +using Xunit; + +namespace StateR; +public class IntegrationTest +{ + public record class CounterState(int Count) : StateBase; + public record class InitialCounterState : IInitialState + { + public CounterState Value => new(0); + } + public record class Increment(int Amount) : IAction; + public record class Decrement(int Amount) : IAction; + public class CounterUpdaters : + IUpdater, + IUpdater + { + public CounterState Update(Increment action, CounterState state) + => state with { Count = state.Count + action.Amount }; + public CounterState Update(Decrement action, CounterState state) + => state with { Count = state.Count - action.Amount }; + } + + public class IncrementTest : IntegrationTest + { + [Fact] + public async Task Should_increment_the_CounterState_by_the_Increment_action_Amount() + { + // Arrange + var services = Initialize(); + var state = services.GetRequiredService>(); + var initialCount = state.Current.Count; + Assert.Equal(0, initialCount); + + var cancellationToken = CancellationToken.None; + var dispatcher = services.GetRequiredService(); + + // Act + await dispatcher.DispatchAsync(new Increment(2), cancellationToken); + + // Assert + Assert.Equal(2, state.Current.Count); + } + } + + public class DecrementTest : IntegrationTest + { + [Fact] + public async Task Should_decrement_the_CounterState_by_the_Increment_action_Amount() + { + // Arrange + var services = Initialize(); + var state = services.GetRequiredService>(); + var initialCount = state.Current.Count; + Assert.Equal(0, initialCount); + + var cancellationToken = CancellationToken.None; + var dispatcher = services.GetRequiredService(); + + // Act + await dispatcher.DispatchAsync(new Decrement(5), cancellationToken); + + // Assert + Assert.Equal(-5, state.Current.Count); + } + } + + private IServiceProvider Initialize() + { + var services = new ServiceCollection(); + services.AddLogging(); + return services.AddStateR() + .AddState() + .AddAction(typeof(Increment)) + .AddAction(typeof(Decrement)) + .AddUpdaters(typeof(CounterUpdaters)) + .Apply() + .BuildServiceProvider() + ; + } +} diff --git a/test/StateR.Tests/StateR.Tests.csproj b/test/StateR.Tests/StateR.Tests.csproj index 7709cc4..24856d1 100644 --- a/test/StateR.Tests/StateR.Tests.csproj +++ b/test/StateR.Tests/StateR.Tests.csproj @@ -12,6 +12,7 @@ + From 36644f7b43a276482fb33de85e45c602571ab55d Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 21:21:59 -0500 Subject: [PATCH 11/17] Cleanup comments --- src/StateR/IStatorBuilder.cs | 8 ---- src/StateR/Internal/StatorBuilder.cs | 17 -------- .../Internal/StatorBuilderTest.cs | 43 ------------------- 3 files changed, 68 deletions(-) diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index cd31de8..297e31f 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -17,15 +17,7 @@ IStatorBuilder AddState() where TInitialState : IInitialState; IStatorBuilder AddState(Type state, Type initialState); - //IStatorBuilder AddAction() - // where TAction : IAction - // where TState : StateBase; IStatorBuilder AddAction(Type actionType); - - //IStatorBuilder AddUpdater() - // where TUpdater : IUpdater - // where TAction : IAction - // where TState : StateBase; IStatorBuilder AddUpdaters(Type updaterType); } diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index ef47bbc..d09710f 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -77,14 +77,6 @@ public IStatorBuilder AddState(Type state, Type initialState) return this; } - //public IStatorBuilder AddAction() - // where TAction : IAction - // where TState : StateBase - //{ - // _actions.Add(typeof(TAction)); - // return this; - //} - public IStatorBuilder AddAction(Type actionType) { if(!IsAction(actionType)) @@ -95,15 +87,6 @@ public IStatorBuilder AddAction(Type actionType) return this; } - //public IStatorBuilder AddUpdater() - // where TUpdater : IUpdater - // where TAction : IAction - // where TState : StateBase - //{ - // _updaters.Add(typeof(TUpdater)); - // return this; - //} - public IStatorBuilder AddUpdaters(Type updaterType) { if(!IsUpdater(updaterType)) diff --git a/test/StateR.Tests/Internal/StatorBuilderTest.cs b/test/StateR.Tests/Internal/StatorBuilderTest.cs index 51b6560..f966ca6 100644 --- a/test/StateR.Tests/Internal/StatorBuilderTest.cs +++ b/test/StateR.Tests/Internal/StatorBuilderTest.cs @@ -74,25 +74,6 @@ public void Should_throw_an_InvalidInitialStateException_when_the_initialState_i } } - //public class AddAction_TAction_TState - //{ - // [Fact] - // public void Should_add_TAction_to_Actions() - // { - // // Arrange - // var services = new ServiceCollection(); - // var sut = new StatorBuilder(services); - - // // Act - // sut.AddAction(); - - // // Assert - // Assert.Collection(sut.Actions, - // type => Assert.Equal(typeof(TestAction1), type) - // ); - // } - //} - public class AddAction_Type { [Fact] @@ -126,25 +107,6 @@ public void Should_throw_an_InvalidActionException_when_actionType_is_invalid(Ty } } - //public class AddUpdater_TUpdater_TAction_TState - //{ - // [Fact] - // public void Should_add_TUpdater_to_Updaters() - // { - // // Arrange - // var services = new ServiceCollection(); - // var sut = new StatorBuilder(services); - - // // Act - // sut.AddUpdater(); - - // // Assert - // Assert.Collection(sut.Updaters, - // type => Assert.Equal(typeof(TestUpdaters), type) - // ); - // } - //} - public class AddUpdaters_Type { [Fact] @@ -176,11 +138,6 @@ public void Should_throw_an_InvalidUpdaterException_when_updaterType_is_invalid( var ex = Assert.Throws(() => sut.AddUpdaters(updaterType)); Assert.Same(updaterType, ex.UpdaterType); } - - //where TUpdater : IUpdater - //where TAction : IAction - //where TState : StateBase - } public class AddTypes : StatorBuilderTest From 53bd1567002b25debeb96a90538f75bb7f8d976b Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 21:42:43 -0500 Subject: [PATCH 12/17] Extract DI from main project --- StateR.sln | 15 ++++++++++ .../CounterApp/CounterApp/CounterApp.csproj | 1 + .../StateValidationDecorator.cs | 28 +++++++++---------- .../Internal/TypeScannerBuilderExtensions.cs | 0 ...soft.Extensions.DependencyInjection.csproj | 17 +++++++++++ .../StatorStartupExtensions.cs | 0 src/StateR/StateR.csproj | 1 - test/StateR.Tests/StateR.Tests.csproj | 2 +- 8 files changed, 48 insertions(+), 16 deletions(-) rename src/{StateR => StateR.Microsoft.Extensions.DependencyInjection}/Internal/TypeScannerBuilderExtensions.cs (100%) create mode 100644 src/StateR.Microsoft.Extensions.DependencyInjection/StateR.Microsoft.Extensions.DependencyInjection.csproj rename src/{StateR => StateR.Microsoft.Extensions.DependencyInjection}/StatorStartupExtensions.cs (100%) diff --git a/StateR.sln b/StateR.sln index 4dad392..78e4f50 100644 --- a/StateR.sln +++ b/StateR.sln @@ -25,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CounterApp", "samples\Count EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CounterApp.Tests", "samples\CounterApp\CounterApp.Tests\CounterApp.Tests.csproj", "{FBDEBA94-7F63-4CB5-AC13-4D0874730316}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StateR.Microsoft.Extensions.DependencyInjection", "src\StateR.Microsoft.Extensions.DependencyInjection\StateR.Microsoft.Extensions.DependencyInjection.csproj", "{5C432129-E637-4895-895D-1FDFDC61C049}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -119,6 +121,18 @@ Global {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Release|x64.Build.0 = Release|Any CPU {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Release|x86.ActiveCfg = Release|Any CPU {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Release|x86.Build.0 = Release|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Debug|x64.ActiveCfg = Debug|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Debug|x64.Build.0 = Debug|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Debug|x86.ActiveCfg = Debug|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Debug|x86.Build.0 = Debug|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Release|Any CPU.Build.0 = Release|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Release|x64.ActiveCfg = Release|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Release|x64.Build.0 = Release|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Release|x86.ActiveCfg = Release|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -132,6 +146,7 @@ Global {E72B8B55-3F7B-4EE2-955B-B5FAB222EBD9} = {22B777AC-AFAE-422A-B4CD-48C907251D9E} {99926EB0-84F9-4906-8F7E-4E1873A403EB} = {E72B8B55-3F7B-4EE2-955B-B5FAB222EBD9} {FBDEBA94-7F63-4CB5-AC13-4D0874730316} = {E72B8B55-3F7B-4EE2-955B-B5FAB222EBD9} + {5C432129-E637-4895-895D-1FDFDC61C049} = {F0F6A2CA-0972-43BD-B777-B5656DFE20C3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6ADD1933-C449-475E-9409-AD333C1C48A0} diff --git a/samples/CounterApp/CounterApp/CounterApp.csproj b/samples/CounterApp/CounterApp/CounterApp.csproj index 74fa298..5317fde 100644 --- a/samples/CounterApp/CounterApp/CounterApp.csproj +++ b/samples/CounterApp/CounterApp/CounterApp.csproj @@ -21,6 +21,7 @@ + diff --git a/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs b/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs index 9468541..5769a29 100644 --- a/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs +++ b/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs @@ -50,7 +50,7 @@ public static class StateValidatorStartupExtensions { public static IStatorBuilder AddStateValidation(this IStatorBuilder builder) { - RegisterStateDecorator(builder.Services, builder.All); + //RegisterStateDecorator(builder.Services, builder.All); //ActionHandlerDecorator(builder.Services); return builder; } @@ -61,20 +61,20 @@ public static IStatorBuilder AddStateValidation(this IStatorBuilder builder) // services.Decorate(); //} - private static void RegisterStateDecorator(IServiceCollection services, IEnumerable types) - { - var states = TypeScanner.FindStates(types); - Console.WriteLine("StateValidator:"); - foreach (var state in states) - { - Console.WriteLine($"- Decorate, StateValidationDecorator<{state.GetStatorName()}>>()"); + //private static void RegisterStateDecorator(IServiceCollection services, IEnumerable types) + //{ + // var states = TypeScanner.FindStates(types); + // Console.WriteLine("StateValidator:"); + // foreach (var state in states) + // { + // Console.WriteLine($"- Decorate, StateValidationDecorator<{state.GetStatorName()}>>()"); - // Equivalent to: Decorate, StateValidationDecorator>(); - var stateType = typeof(IState<>).MakeGenericType(state); - var stateSessionDecoratorType = typeof(StateValidationDecorator<>).MakeGenericType(state); - services.Decorate(stateType, stateSessionDecoratorType); - } - } + // // Equivalent to: Decorate, StateValidationDecorator>(); + // var stateType = typeof(IState<>).MakeGenericType(state); + // var stateSessionDecoratorType = typeof(StateValidationDecorator<>).MakeGenericType(state); + // services.Decorate(stateType, stateSessionDecoratorType); + // } + //} } //public class ValidationExceptionActionHandlersManagerDecorator : IActionFilter diff --git a/src/StateR/Internal/TypeScannerBuilderExtensions.cs b/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs similarity index 100% rename from src/StateR/Internal/TypeScannerBuilderExtensions.cs rename to src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs diff --git a/src/StateR.Microsoft.Extensions.DependencyInjection/StateR.Microsoft.Extensions.DependencyInjection.csproj b/src/StateR.Microsoft.Extensions.DependencyInjection/StateR.Microsoft.Extensions.DependencyInjection.csproj new file mode 100644 index 0000000..b289158 --- /dev/null +++ b/src/StateR.Microsoft.Extensions.DependencyInjection/StateR.Microsoft.Extensions.DependencyInjection.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + enable + StateR + + + + + + + + + + diff --git a/src/StateR/StatorStartupExtensions.cs b/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs similarity index 100% rename from src/StateR/StatorStartupExtensions.cs rename to src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs diff --git a/src/StateR/StateR.csproj b/src/StateR/StateR.csproj index 37d1c0a..ad8857a 100644 --- a/src/StateR/StateR.csproj +++ b/src/StateR/StateR.csproj @@ -9,7 +9,6 @@ - diff --git a/test/StateR.Tests/StateR.Tests.csproj b/test/StateR.Tests/StateR.Tests.csproj index 24856d1..06ff3ba 100644 --- a/test/StateR.Tests/StateR.Tests.csproj +++ b/test/StateR.Tests/StateR.Tests.csproj @@ -27,7 +27,7 @@ - + From 504c9c21dedef8e013df9910144036044cc8c333 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 21:45:26 -0500 Subject: [PATCH 13/17] AddState now add initialState properly --- src/StateR/Internal/StatorBuilder.cs | 1 + test/StateR.Tests/Internal/StatorBuilderTest.cs | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index d09710f..35c383d 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -74,6 +74,7 @@ public IStatorBuilder AddState(Type state, Type initialState) throw new InvalidInitialStateException(state, initialState); } _states.Add(state); + _initialStates.Add(initialState); return this; } diff --git a/test/StateR.Tests/Internal/StatorBuilderTest.cs b/test/StateR.Tests/Internal/StatorBuilderTest.cs index f966ca6..9379f75 100644 --- a/test/StateR.Tests/Internal/StatorBuilderTest.cs +++ b/test/StateR.Tests/Internal/StatorBuilderTest.cs @@ -30,7 +30,7 @@ public void Should_add_TState_to_States_and_TInitialState_to_InitialStates() public class AddState_Type : StatorBuilderTest { [Fact] - public void Should_add_a_valid_state_type_to_States() + public void Should_add_a_valid_state_type_to_States_and_valid_initialState_to_InitialStates() { // Arrange var services = new ServiceCollection(); @@ -43,6 +43,9 @@ public void Should_add_a_valid_state_type_to_States() Assert.Collection(sut.States, type => Assert.Equal(typeof(TestState1), type) ); + Assert.Collection(sut.InitialStates, + type => Assert.Equal(typeof(InitialTestState1), type) + ); } [Fact] From 104b83ba4bc04b3c33af38556dc22fdc1ea5df06 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 22:03:56 -0500 Subject: [PATCH 14/17] Add ActionFilter to StatorBuilder --- samples/CounterApp/CounterApp/Program.cs | 2 +- .../StatorStartupExtensions.cs | 20 ++++----- src/StateR/IStatorBuilder.cs | 3 +- src/StateR/Internal/StatorBuilder.cs | 43 ++++++++++++++----- .../Internal/StatorBuilderTest.cs | 37 +++++++++++++++- test/StateR.Tests/TestTypes.cs | 16 ++++++- 6 files changed, 95 insertions(+), 26 deletions(-) diff --git a/samples/CounterApp/CounterApp/Program.cs b/samples/CounterApp/CounterApp/Program.cs index 9ee5a23..4586b09 100644 --- a/samples/CounterApp/CounterApp/Program.cs +++ b/samples/CounterApp/CounterApp/Program.cs @@ -39,7 +39,7 @@ public static void RegisterServices(this IServiceCollection services) { var appAssembly = typeof(App).Assembly; services - .AddStateR(appAssembly) + .AddStateR() //.AddAsyncOperations() //.AddReduxDevTools() .AddFluentValidation(appAssembly) diff --git a/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs b/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs index 5df5292..949b8b2 100644 --- a/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs +++ b/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs @@ -28,18 +28,18 @@ public static IStatorBuilder AddStateR(this IServiceCollection services) return new StatorBuilder(services); } - public static IStatorBuilder AddStateR(this IServiceCollection services, params Assembly[] assembliesToScanForStates) - { - var builder = services.AddStateR(); - var allTypes = assembliesToScanForStates - .SelectMany(a => a.GetTypes()); - //builder.AddTypes(allTypes); + //public static IStatorBuilder AddStateR(this IServiceCollection services, params Assembly[] assembliesToScanForStates) + //{ + // var builder = services.AddStateR(); + // var allTypes = assembliesToScanForStates + // .SelectMany(a => a.GetTypes()); + // //builder.AddTypes(allTypes); - var states = TypeScanner.FindStates(allTypes); - builder.AddStates(states); + // var states = TypeScanner.FindStates(allTypes); + // builder.AddStates(states); - return builder; - } + // return builder; + //} //public static IStatorBuilder AddMiddleware(this IStatorBuilder builder) //{ diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index 297e31f..a841bb3 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.DependencyInjection; -using StateR.Updaters; using System.Collections.ObjectModel; namespace StateR; @@ -11,6 +10,7 @@ public interface IStatorBuilder : IOldStatorBuilder ReadOnlyCollection InitialStates { get; } ReadOnlyCollection Actions { get; } ReadOnlyCollection Updaters { get; } + ReadOnlyCollection ActionFilters { get; } IStatorBuilder AddState() where TState : StateBase @@ -19,6 +19,7 @@ IStatorBuilder AddState() IStatorBuilder AddAction(Type actionType); IStatorBuilder AddUpdaters(Type updaterType); + IStatorBuilder AddActionFilter(Type actionFilterType); } public interface IOldStatorBuilder diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index 35c383d..22585e8 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using StateR.Pipeline; using StateR.Updaters; using System.Collections.ObjectModel; @@ -10,6 +11,7 @@ public class StatorBuilder : IStatorBuilder private readonly List _initialStates = new(); private readonly List _actions = new(); private readonly List _updaters = new(); + private readonly List _actionFilters = new(); public StatorBuilder(IServiceCollection services) { @@ -53,6 +55,7 @@ private IStatorBuilder AddDistinctTypes(List list, IEnumerable types public ReadOnlyCollection InitialStates => new(_initialStates); public ReadOnlyCollection Actions => new(_actions); public ReadOnlyCollection Updaters => new(_updaters); + public ReadOnlyCollection ActionFilters => new(_actionFilters); public IStatorBuilder AddState() where TState : StateBase @@ -80,7 +83,7 @@ public IStatorBuilder AddState(Type state, Type initialState) public IStatorBuilder AddAction(Type actionType) { - if(!IsAction(actionType)) + if (!IsAction(actionType)) { throw new InvalidActionException(actionType); } @@ -90,7 +93,7 @@ public IStatorBuilder AddAction(Type actionType) public IStatorBuilder AddUpdaters(Type updaterType) { - if(!IsUpdater(updaterType)) + if (!IsUpdater(updaterType)) { throw new InvalidUpdaterException(updaterType); } @@ -98,22 +101,29 @@ public IStatorBuilder AddUpdaters(Type updaterType) return this; } - private static readonly Type _iActionType = typeof(IAction<>); - private static bool IsAction(Type actionType) + public IStatorBuilder AddActionFilter(Type actionFilterType) { - var interfaces = actionType.GetInterfaces() - .Count(i => i.IsGenericType && i.GetGenericTypeDefinition() == _iActionType); - return interfaces > 0; + if (!IsActionFilter(actionFilterType)) + { + throw new InvalidActionFilterException(actionFilterType); + } + _actionFilters.Add(actionFilterType); + return this; } - private static readonly Type _iUpdaterType = typeof(IUpdater<,>); + private static bool IsAction(Type actionType) + => HasGenericInterface(actionType, typeof(IAction<>)); private static bool IsUpdater(Type updaterType) + => HasGenericInterface(updaterType, typeof(IUpdater<,>)); + private static bool IsActionFilter(Type actionFilterType) + => HasGenericInterface(actionFilterType, typeof(IActionFilter<,>)); + + private static bool HasGenericInterface(Type type, Type interfaceType) { - var interfaces = updaterType.GetInterfaces() - .Count(i => i.IsGenericType && i.GetGenericTypeDefinition() == _iUpdaterType); + var interfaces = type.GetInterfaces() + .Count(i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType); return interfaces > 0; } - } public class InvalidStateException : Exception @@ -159,4 +169,15 @@ public InvalidUpdaterException(Type updaterType) } public Type UpdaterType { get; } +} + +public class InvalidActionFilterException : Exception +{ + public InvalidActionFilterException(Type actionFilterType) + : base($"The type {actionFilterType.Name} is not a valid IActionFilter.") + { + ActionFilterType = actionFilterType; + } + + public Type ActionFilterType { get; } } \ No newline at end of file diff --git a/test/StateR.Tests/Internal/StatorBuilderTest.cs b/test/StateR.Tests/Internal/StatorBuilderTest.cs index 9379f75..3726384 100644 --- a/test/StateR.Tests/Internal/StatorBuilderTest.cs +++ b/test/StateR.Tests/Internal/StatorBuilderTest.cs @@ -77,7 +77,7 @@ public void Should_throw_an_InvalidInitialStateException_when_the_initialState_i } } - public class AddAction_Type + public class AddAction : StatorBuilderTest { [Fact] public void Should_add_TAction_to_Actions() @@ -110,7 +110,7 @@ public void Should_throw_an_InvalidActionException_when_actionType_is_invalid(Ty } } - public class AddUpdaters_Type + public class AddUpdaters : StatorBuilderTest { [Fact] public void Should_add_updaterType_to_Updaters() @@ -143,6 +143,39 @@ public void Should_throw_an_InvalidUpdaterException_when_updaterType_is_invalid( } } + public class AddActionFilter : StatorBuilderTest + { + [Fact] + public void Should_add_actionFilterType_to_ActionFilters() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + var actionFilterType = typeof(TestActionFilter); + + // Act + sut.AddActionFilter(actionFilterType); + + // Assert + Assert.Collection(sut.ActionFilters, + type => Assert.Same(actionFilterType, type) + ); + } + + [Theory] + [InlineData(typeof(NotAnActionFilter))] + public void Should_throw_an_InvalidActionFilterException_when_actionFilterType_is_invalid(Type actionFilterType) + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + + // Act & Assert + var ex = Assert.Throws(() => sut.AddActionFilter(actionFilterType)); + Assert.Same(actionFilterType, ex.ActionFilterType); + } + } + public class AddTypes : StatorBuilderTest { [Fact] diff --git a/test/StateR.Tests/TestTypes.cs b/test/StateR.Tests/TestTypes.cs index df2ecb1..530365d 100644 --- a/test/StateR.Tests/TestTypes.cs +++ b/test/StateR.Tests/TestTypes.cs @@ -1,4 +1,5 @@ -using StateR.Updaters; +using StateR.Pipeline; +using StateR.Updaters; namespace StateR; @@ -22,11 +23,24 @@ public record class InitialTestState3 : IInitialState public class NotAState { } public class NotAnAction { } public class NotAnUpdater { } +public class NotAnActionFilter { } public record TestAction1 : IAction; +public record TestAction2 : IAction; public class TestUpdaters : IUpdater { public TestState1 Update(TestAction1 action, TestState1 state) => new(); +} + +public class TestActionFilter : IActionFilter +{ + public Task InvokeAsync( + IDispatchContext context, + ActionDelegate next, + CancellationToken cancellationToken) + { + return Task.CompletedTask; + } } \ No newline at end of file From 7461853a5a20e342e8c897e93861e4e647fd2382 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Thu, 3 Mar 2022 00:11:57 -0500 Subject: [PATCH 15/17] Add support for pipeline action filters --- .../Internal/TypeScannerBuilderExtensions.cs | 11 +++++ .../StatorStartupExtensions.cs | 46 +++++++++++++++---- src/StateR/Dispatcher.cs | 14 +++--- src/StateR/Pipeline/IActionFilterFactory.cs | 24 ---------- src/StateR/Pipeline/IPipelineFactory.cs | 35 ++++++++++++++ test/StateR.Tests/IntegrationTest.cs | 36 +++++++++++++++ .../Internal/StatorBuilderTest.cs | 4 +- .../StatorStartupExtensionsTest.cs | 29 +++++++++++- test/StateR.Tests/TestTypes.cs | 20 +++++++- 9 files changed, 175 insertions(+), 44 deletions(-) delete mode 100644 src/StateR/Pipeline/IActionFilterFactory.cs create mode 100644 src/StateR/Pipeline/IPipelineFactory.cs diff --git a/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs b/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs index 1365f31..c2d9121 100644 --- a/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs +++ b/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs @@ -32,6 +32,17 @@ public static IEnumerable FindStates(IEnumerable types) return states; } + public static IEnumerable FindInitialStates(IEnumerable types) + { + var initialStates = types + .Where(type => !type.IsAbstract && type + .GetTypeInfo() + .GetInterfaces() + .Any(i => i == typeof(IInitialState<>)) + ); + return initialStates; + } + public static IEnumerable FindActions(IEnumerable types) { var actions = types diff --git a/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs b/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs index 949b8b2..0c47f7b 100644 --- a/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs +++ b/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs @@ -14,18 +14,24 @@ public static IStatorBuilder AddStateR(this IServiceCollection services) services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddSingleton(); - //services.TryAddSingleton(); - //services.TryAddSingleton(); - //services.TryAddSingleton(); + return new StatorBuilder(services); + } - //services.TryAddSingleton(); - //services.TryAddSingleton(); - //services.TryAddSingleton(); - //services.TryAddSingleton(); + public static IStatorBuilder ScanAndAddStates(this IStatorBuilder builder, params Assembly[] assembliesToScan) + { + var allTypes = assembliesToScan + .SelectMany(a => a.GetTypes()); + var initialStates = TypeScanner.FindInitialStates(allTypes); - return new StatorBuilder(services); + foreach (var initialState in initialStates) + { + var state = initialState.GenericTypeArguments[0]; + builder.AddState(state, initialState); + } + + return builder; } //public static IStatorBuilder AddStateR(this IServiceCollection services, params Assembly[] assembliesToScanForStates) @@ -95,6 +101,28 @@ public static IServiceCollection Apply(this IStatorBuilder builder, Action); + foreach (var filter in builder.ActionFilters) + { + var interfaces = filter.GetInterfaces() + .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == iActionFilterType); + foreach (var @interface in interfaces) + { + var actionType = @interface.GenericTypeArguments[0]; + var stateType = @interface.GenericTypeArguments[1]; + var filterType = iActionFilterType.MakeGenericType(actionType, stateType); + + builder.Services.AddSingleton(@interface, filter); + } + } + + /* + + public ActionDelegate Create(IDispatchContext context, CancellationToken cancellationToken) + { + + } + */ return builder.Services; diff --git a/src/StateR/Dispatcher.cs b/src/StateR/Dispatcher.cs index 63eff9d..2d3836b 100644 --- a/src/StateR/Dispatcher.cs +++ b/src/StateR/Dispatcher.cs @@ -7,13 +7,13 @@ namespace StateR; public class Dispatcher : IDispatcher { private readonly IDispatchContextFactory _dispatchContextFactory; - private readonly IActionFilterFactory _actionFilterFactory; + private readonly IPipelineFactory _pipelineFactory; private readonly ILogger _logger; - public Dispatcher(IDispatchContextFactory dispatchContextFactory, IActionFilterFactory actionFilterFactory, ILogger logger) + public Dispatcher(IDispatchContextFactory dispatchContextFactory, IPipelineFactory actionFilterFactory, ILogger logger) { _dispatchContextFactory = dispatchContextFactory ?? throw new ArgumentNullException(nameof(dispatchContextFactory)); - _actionFilterFactory = actionFilterFactory ?? throw new ArgumentNullException(nameof(actionFilterFactory)); + _pipelineFactory = actionFilterFactory ?? throw new ArgumentNullException(nameof(actionFilterFactory)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -23,10 +23,10 @@ public async Task DispatchAsync(TAction action, CancellationTok { using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var dispatchContext = _dispatchContextFactory.Create(action, this, cancellationTokenSource); - var actionFilter = _actionFilterFactory.Create(dispatchContext); + var pipeline = _pipelineFactory.Create(dispatchContext); try { - await actionFilter.InvokeAsync(dispatchContext, null, cancellationToken); + await pipeline.Invoke(dispatchContext, cancellationToken).ConfigureAwait(false); } catch (DispatchCancelledException ex) { @@ -46,7 +46,9 @@ public Task DispatchAsync(object action, CancellationToken cancellationToken) throw new InvalidOperationException($"The action must implement the {typeof(IAction<>).Name} interface."); } var stateType = actionInterface.GetGenericArguments()[0]; - var method = GetType().GetMethods().FirstOrDefault(m => m.IsGenericMethod && m.Name == nameof(DispatchAsync)); + var method = GetType() + .GetMethods() + .FirstOrDefault(m => m.IsGenericMethod && m.Name == nameof(DispatchAsync)); if(method == null) { throw new MissingMethodException(nameof(Dispatcher), nameof(DispatchAsync)); diff --git a/src/StateR/Pipeline/IActionFilterFactory.cs b/src/StateR/Pipeline/IActionFilterFactory.cs deleted file mode 100644 index 19da883..0000000 --- a/src/StateR/Pipeline/IActionFilterFactory.cs +++ /dev/null @@ -1,24 +0,0 @@ - -using Microsoft.Extensions.DependencyInjection; -namespace StateR.Pipeline; - -public interface IActionFilterFactory -{ - IActionFilter Create(IDispatchContext context) - where TAction : IAction - where TState : StateBase; -} - -public class ActionFilterFactory : IActionFilterFactory -{ - private readonly IServiceProvider _serviceProvider; - public ActionFilterFactory(IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - } - - public IActionFilter Create(IDispatchContext context) - where TAction : IAction - where TState : StateBase - => _serviceProvider.GetRequiredService>(); -} \ No newline at end of file diff --git a/src/StateR/Pipeline/IPipelineFactory.cs b/src/StateR/Pipeline/IPipelineFactory.cs new file mode 100644 index 0000000..942a36e --- /dev/null +++ b/src/StateR/Pipeline/IPipelineFactory.cs @@ -0,0 +1,35 @@ + +using Microsoft.Extensions.DependencyInjection; +namespace StateR.Pipeline; + +public interface IPipelineFactory +{ + ActionDelegate Create(IDispatchContext context) + where TAction : IAction + where TState : StateBase; +} + +public class PipelineFactory : IPipelineFactory +{ + private readonly IServiceProvider _serviceProvider; + public PipelineFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + } + + public ActionDelegate Create(IDispatchContext context) + where TAction : IAction + where TState : StateBase + { + var filters = _serviceProvider.GetServices>(); + var enumerator = filters.GetEnumerator(); + enumerator.MoveNext(); + return MakeDelegate(enumerator.Current); + + ActionDelegate MakeDelegate(IActionFilter filter) + { + var hasNext = enumerator.MoveNext(); + return new((a, s) => filter.InvokeAsync(a, hasNext ? MakeDelegate(enumerator.Current) : null, s)); + } + } +} \ No newline at end of file diff --git a/test/StateR.Tests/IntegrationTest.cs b/test/StateR.Tests/IntegrationTest.cs index 82acb87..4bc6853 100644 --- a/test/StateR.Tests/IntegrationTest.cs +++ b/test/StateR.Tests/IntegrationTest.cs @@ -1,7 +1,9 @@ using Microsoft.Extensions.DependencyInjection; using StateR.Internal; +using StateR.Pipeline; using StateR.Updaters; using System; +using System.Threading.Tasks; using Xunit; namespace StateR; @@ -23,6 +25,21 @@ public CounterState Update(Increment action, CounterState state) public CounterState Update(Decrement action, CounterState state) => state with { Count = state.Count - action.Amount }; } + public class ValidateIncrementFilter : IActionFilter + { + public Task InvokeAsync( + IDispatchContext context, + ActionDelegate next, + CancellationToken cancellationToken) + { + if (context.Action.Amount <= 0) + { + throw new ValidationException(); + } + return next?.Invoke(context, cancellationToken); + } + } + public class ValidationException : Exception { } public class IncrementTest : IntegrationTest { @@ -68,6 +85,24 @@ public async Task Should_decrement_the_CounterState_by_the_Increment_action_Amou } } + public class ValidateIncrementFilterTest : IntegrationTest + { + [Theory] + [InlineData(0)] + [InlineData(-1)] + public async Task Should_throw_a_ValidationException_when_Increment_is_smaller_or_equal_to_zero(int amount) + { + // Arrange + var services = Initialize(); + var cancellationToken = CancellationToken.None; + var dispatcher = services.GetRequiredService(); + + // Act & Assert + await Assert.ThrowsAsync(() => dispatcher + .DispatchAsync(new Increment(amount), cancellationToken)); + } + } + private IServiceProvider Initialize() { var services = new ServiceCollection(); @@ -77,6 +112,7 @@ private IServiceProvider Initialize() .AddAction(typeof(Increment)) .AddAction(typeof(Decrement)) .AddUpdaters(typeof(CounterUpdaters)) + .AddActionFilter(typeof(ValidateIncrementFilter)) .Apply() .BuildServiceProvider() ; diff --git a/test/StateR.Tests/Internal/StatorBuilderTest.cs b/test/StateR.Tests/Internal/StatorBuilderTest.cs index 3726384..91d1a18 100644 --- a/test/StateR.Tests/Internal/StatorBuilderTest.cs +++ b/test/StateR.Tests/Internal/StatorBuilderTest.cs @@ -118,7 +118,7 @@ public void Should_add_updaterType_to_Updaters() // Arrange var services = new ServiceCollection(); var sut = new StatorBuilder(services); - var updaterType = typeof(TestUpdaters); + var updaterType = typeof(TestUpdater1); // Act sut.AddUpdaters(updaterType); @@ -151,7 +151,7 @@ public void Should_add_actionFilterType_to_ActionFilters() // Arrange var services = new ServiceCollection(); var sut = new StatorBuilder(services); - var actionFilterType = typeof(TestActionFilter); + var actionFilterType = typeof(TestActionFilter1); // Act sut.AddActionFilter(actionFilterType); diff --git a/test/StateR.Tests/StatorStartupExtensionsTest.cs b/test/StateR.Tests/StatorStartupExtensionsTest.cs index 7c08996..f733f1a 100644 --- a/test/StateR.Tests/StatorStartupExtensionsTest.cs +++ b/test/StateR.Tests/StatorStartupExtensionsTest.cs @@ -55,7 +55,7 @@ public void Should_add_IUpdater_and_IActionFilter_to_the_ServiceCollection() var sut = new StatorBuilder(services) .AddState() .AddAction(typeof(TestAction1)) - .AddUpdaters(typeof(TestUpdaters)) + .AddUpdaters(typeof(TestUpdater1)) ; // Act @@ -66,5 +66,32 @@ public void Should_add_IUpdater_and_IActionFilter_to_the_ServiceCollection() sp.GetRequiredService>(); sp.GetRequiredService>(); } + + [Fact] + public void Should_add_IActionFilter_to_the_ServiceCollection() + { + // Arrange + var services = new ServiceCollection(); + var sp = new StatorBuilder(services) + .AddState() + .AddAction(typeof(TestAction2)) + .AddUpdaters(typeof(TestUpdater2)) + .AddActionFilter(typeof(TestActionFilter1)) + .AddActionFilter(typeof(TestActionFilter2)) + .Apply() + .BuildServiceProvider(); + ; + + // Act + var actionFilters = sp.GetServices>(); + + // Assert + Assert.Collection(actionFilters, + filter => Assert.IsType>(filter), + filter => Assert.IsType(filter), + filter => Assert.IsType(filter) + ); + } + } } diff --git a/test/StateR.Tests/TestTypes.cs b/test/StateR.Tests/TestTypes.cs index 530365d..a49465b 100644 --- a/test/StateR.Tests/TestTypes.cs +++ b/test/StateR.Tests/TestTypes.cs @@ -28,13 +28,29 @@ public class NotAnActionFilter { } public record TestAction1 : IAction; public record TestAction2 : IAction; -public class TestUpdaters : IUpdater +public class TestUpdater1 : IUpdater { public TestState1 Update(TestAction1 action, TestState1 state) => new(); } -public class TestActionFilter : IActionFilter +public class TestUpdater2 : IUpdater +{ + public TestState2 Update(TestAction2 action, TestState2 state) + => new(); +} + +public class TestActionFilter1 : IActionFilter +{ + public Task InvokeAsync( + IDispatchContext context, + ActionDelegate next, + CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} +public class TestActionFilter2 : IActionFilter { public Task InvokeAsync( IDispatchContext context, From 710b14815ff7b6996c17290f09428178641e069e Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Thu, 3 Mar 2022 00:31:59 -0500 Subject: [PATCH 16/17] Statically fix the test program counter and experimental FluentValidation registration --- samples/CounterApp/CounterApp/Program.cs | 9 ++ .../FluentValidation/StartupExtensions.cs | 19 ++- .../FluentValidation/ValidationFilter.cs | 3 +- .../Internal/TypeScannerBuilderExtensions.cs | 30 ++-- .../StatorStartupExtensions.cs | 133 ------------------ src/StateR/IStatorBuilder.cs | 23 +-- src/StateR/Pipeline/IPipelineFactory.cs | 3 + 7 files changed, 38 insertions(+), 182 deletions(-) diff --git a/samples/CounterApp/CounterApp/Program.cs b/samples/CounterApp/CounterApp/Program.cs index 4586b09..0742e60 100644 --- a/samples/CounterApp/CounterApp/Program.cs +++ b/samples/CounterApp/CounterApp/Program.cs @@ -40,6 +40,15 @@ public static void RegisterServices(this IServiceCollection services) var appAssembly = typeof(App).Assembly; services .AddStateR() + + // TODO: scan for types instead + .AddState() + .AddAction(typeof(CounterApp.Features.Counter.Increment)) + .AddAction(typeof(CounterApp.Features.Counter.Decrement)) + .AddAction(typeof(CounterApp.Features.Counter.SetPositive)) + .AddAction(typeof(CounterApp.Features.Counter.SetNegative)) + .AddUpdaters(typeof(CounterApp.Features.Counter.Updaters)) + //.AddAsyncOperations() //.AddReduxDevTools() .AddFluentValidation(appAssembly) diff --git a/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs b/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs index 18df8c8..c7994a1 100644 --- a/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs +++ b/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs @@ -13,18 +13,15 @@ public static IStatorBuilder AddFluentValidation(this IStatorBuilder builder, pa ArgumentNullException.ThrowIfNull(builder, nameof(builder)); ArgumentNullException.ThrowIfNull(assembliesToScan, nameof(assembliesToScan)); - // Validation action - builder.AddTypes(new[] { - typeof(AddValidationErrors), - typeof(ReplaceValidationErrors), - typeof(ValidationUpdaters), - typeof(ValidationInitialState), - typeof(ValidationState), - typeof(ValidationFilter<,>), - }); - builder.AddMiddlewares(new[] { typeof(ValidationFilter<,>) }); + // Add state, actions, and updaters + builder + .AddState() + .AddAction(typeof(AddValidationErrors)) + .AddAction(typeof(ReplaceValidationErrors)) + .AddUpdaters(typeof(ValidationUpdaters)) + ; - // Validation interceptor and state + // Validation interceptor builder.Services.TryAddSingleton(typeof(IActionFilter<,>), typeof(ValidationFilter<,>)); // Scan for validators diff --git a/src/StateR.Experiments/Validations/FluentValidation/ValidationFilter.cs b/src/StateR.Experiments/Validations/FluentValidation/ValidationFilter.cs index 9c836c3..fa14a1c 100644 --- a/src/StateR.Experiments/Validations/FluentValidation/ValidationFilter.cs +++ b/src/StateR.Experiments/Validations/FluentValidation/ValidationFilter.cs @@ -18,7 +18,7 @@ public ValidationFilter(IEnumerable> validators) public async Task InvokeAsync(IDispatchContext context, ActionDelegate? next, CancellationToken cancellationToken) { - ArgumentNullException.ThrowIfNull(next, nameof(next)); + ArgumentNullException.ThrowIfNull(next); var result = _validators .Select(validator => validator.Validate(context.Action)); @@ -36,6 +36,7 @@ public async Task InvokeAsync(IDispatchContext context, ActionD } catch (ValidationException ex) { + Console.WriteLine(ex.Message); await context.Dispatcher.DispatchAsync( new AddValidationErrors(ex.Errors), context.CancellationToken diff --git a/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs b/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs index c2d9121..d32f824 100644 --- a/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs +++ b/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs @@ -4,25 +4,25 @@ namespace StateR.Internal; -public static class TypeScannerBuilderExtensions -{ - public static IStatorBuilder ScanTypes(this IStatorBuilder builder) - { - var states = TypeScanner.FindStates(builder.All); - builder.AddStates(states); +//public static class TypeScannerBuilderExtensions +//{ +// public static IStatorBuilder ScanTypes(this IStatorBuilder builder) +// { +// var states = TypeScanner.FindStates(builder.All); +// builder.AddStates(states); - var actions = TypeScanner.FindActions(builder.All); - builder.AddActions(actions); +// var actions = TypeScanner.FindActions(builder.All); +// builder.AddActions(actions); - var updaters = TypeScanner.FindUpdaters(builder.All); - builder.AddUpdaters(updaters); +// var updaters = TypeScanner.FindUpdaters(builder.All); +// builder.AddUpdaters(updaters); - var actionHandlers = TypeScanner.FindMiddlewares(builder.All); - builder.AddUpdaters(actionHandlers); +// var actionHandlers = TypeScanner.FindMiddlewares(builder.All); +// builder.AddUpdaters(actionHandlers); - return builder; - } -} +// return builder; +// } +//} public static class TypeScanner { public static IEnumerable FindStates(IEnumerable types) diff --git a/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs b/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs index 0c47f7b..6a7d712 100644 --- a/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs +++ b/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs @@ -34,24 +34,6 @@ public static IStatorBuilder ScanAndAddStates(this IStatorBuilder builder, param return builder; } - //public static IStatorBuilder AddStateR(this IServiceCollection services, params Assembly[] assembliesToScanForStates) - //{ - // var builder = services.AddStateR(); - // var allTypes = assembliesToScanForStates - // .SelectMany(a => a.GetTypes()); - // //builder.AddTypes(allTypes); - - // var states = TypeScanner.FindStates(allTypes); - // builder.AddStates(states); - - // return builder; - //} - - //public static IStatorBuilder AddMiddleware(this IStatorBuilder builder) - //{ - - //} - public static IServiceCollection Apply(this IStatorBuilder builder, Action? postConfiguration = null) { // Register States @@ -116,121 +98,6 @@ public static IServiceCollection Apply(this IStatorBuilder builder, Action Create(IDispatchContext context, CancellationToken cancellationToken) - { - - } - */ - - return builder.Services; - - // Extract types - builder.ScanTypes(); - - // Scan - builder.Services.Scan(s => s - .AddTypes(builder.All) - - // Equivalent to: AddSingleton, Implementation>(); - .AddClasses(classes => classes.AssignableTo(typeof(IInitialState<>))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - - //// Equivalent to: AddSingleton(); - //.AddClasses(classes => classes.AssignableTo(typeof(IBeforeInterceptorHook))) - //.AsImplementedInterfaces() - //.WithSingletonLifetime() - //// Equivalent to: AddSingleton(); - //.AddClasses(classes => classes.AssignableTo(typeof(IAfterInterceptorHook))) - //.AsImplementedInterfaces() - //.WithSingletonLifetime() - - //// Equivalent to: AddSingleton(); - //.AddClasses(classes => classes.AssignableTo(typeof(IBeforeAfterEffectHook))) - //.AsImplementedInterfaces() - //.WithSingletonLifetime() - //// Equivalent to: AddSingleton(); - //.AddClasses(classes => classes.AssignableTo(typeof(IAfterAfterEffectHook))) - //.AsImplementedInterfaces() - //.WithSingletonLifetime() - - //// Equivalent to: AddSingleton(); - //.AddClasses(classes => classes.AssignableTo(typeof(IBeforeActionHook))) - //.AsImplementedInterfaces() - //.WithSingletonLifetime() - //// Equivalent to: AddSingleton(); - //.AddClasses(classes => classes.AssignableTo(typeof(IAfterActionHook))) - //.AsImplementedInterfaces() - //.WithSingletonLifetime() - - //// Equivalent to: AddSingleton(); - //.AddClasses(classes => classes.AssignableTo(typeof(IBeforeUpdateHook))) - //.AsImplementedInterfaces() - //.WithSingletonLifetime() - //// Equivalent to: AddSingleton(); - //.AddClasses(classes => classes.AssignableTo(typeof(IAfterUpdateHook))) - //.AsImplementedInterfaces() - //.WithSingletonLifetime() - - //// Equivalent to: AddSingleton, Implementation>(); - //.AddClasses(classes => classes.AssignableTo(typeof(IInterceptor<>))) - //.AsImplementedInterfaces() - //.WithSingletonLifetime() - - //// Equivalent to: AddSingleton, Implementation>(); - //.AddClasses(classes => classes.AssignableTo(typeof(IAfterEffects<>))) - //.AsImplementedInterfaces() - //.WithSingletonLifetime() - ); - - // Register States - foreach (var state in builder.States) - { - Console.WriteLine($"state: {state.FullName}"); - - // Equivalent to: AddSingleton, State>(); - var stateServiceType = typeof(IState<>).MakeGenericType(state); - var stateImplementationType = typeof(State<>).MakeGenericType(state); - builder.Services.AddSingleton(stateServiceType, stateImplementationType); - } - - //// Register Updaters and their respective IMiddleware - //var iUpdaterType = typeof(IUpdater<,>); - //var updaterHandler = typeof(UpdaterMiddleware<,>); - //var handlerType = typeof(IActionFilter<,>); - //foreach (var updater in builder.Updaters) - //{ - // Console.WriteLine($"updater: {updater.FullName}"); - // var interfaces = updater.GetInterfaces() - // .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == iUpdaterType); - // foreach (var @interface in interfaces) - // { - // // Equivalent to: AddSingleton, UpdaterMiddleware> - // var actionType = @interface.GenericTypeArguments[0]; - // var stateType = @interface.GenericTypeArguments[1]; - // var iMiddlewareServiceType = handlerType.MakeGenericType(actionType, stateType); - // var updaterMiddlewareImplementationType = updaterHandler.MakeGenericType(stateType, actionType); - // builder.Services.AddSingleton(iMiddlewareServiceType, updaterMiddlewareImplementationType); - - // // Equivalent to: AddSingleton, Updater>(); - // builder.Services.AddSingleton(@interface, updater); - - // Console.WriteLine($"- AddSingleton<{iMiddlewareServiceType.GetStatorName()}, {updaterMiddlewareImplementationType.GetStatorName()}>()"); - // Console.WriteLine($"- AddSingleton<{@interface.GetStatorName()}, {updater.GetStatorName()}>()"); - // } - //} - - // Register Middleware - foreach (var middleware in builder.Middlewares.AsEnumerable().Reverse()) - { - builder.Services.Decorate(typeof(IActionFilter<,>), middleware); - } - - // Run post-configuration - postConfiguration?.Invoke(builder); - return builder.Services; } } diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index a841bb3..25beaf9 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -3,7 +3,7 @@ namespace StateR; -public interface IStatorBuilder : IOldStatorBuilder +public interface IStatorBuilder { IServiceCollection Services { get; } ReadOnlyCollection States { get; } @@ -21,24 +21,3 @@ IStatorBuilder AddState() IStatorBuilder AddUpdaters(Type updaterType); IStatorBuilder AddActionFilter(Type actionFilterType); } - -public interface IOldStatorBuilder -{ - //List Interceptors { get; } - List ActionHandlers { get; } - //List AfterEffects { get; } - List All { get; } - - IStatorBuilder AddTypes(IEnumerable types); - IStatorBuilder AddStates(IEnumerable states); - IStatorBuilder AddActions(IEnumerable states); - IStatorBuilder AddUpdaters(IEnumerable states); - IStatorBuilder AddActionHandlers(IEnumerable types); - - IStatorBuilder AddMiddlewares(IEnumerable types); - List Middlewares { get; } - - //IStatorBuilder AddAction() - // where TState : StateBase - // where TAction : IAction; -} diff --git a/src/StateR/Pipeline/IPipelineFactory.cs b/src/StateR/Pipeline/IPipelineFactory.cs index 942a36e..0158880 100644 --- a/src/StateR/Pipeline/IPipelineFactory.cs +++ b/src/StateR/Pipeline/IPipelineFactory.cs @@ -1,5 +1,7 @@  using Microsoft.Extensions.DependencyInjection; +using StateR.Internal; + namespace StateR.Pipeline; public interface IPipelineFactory @@ -29,6 +31,7 @@ public ActionDelegate Create(IDispatchContext< ActionDelegate MakeDelegate(IActionFilter filter) { var hasNext = enumerator.MoveNext(); + Console.WriteLine($"ActionDelegate: {filter.GetType().GetStatorName()}"); return new((a, s) => filter.InvokeAsync(a, hasNext ? MakeDelegate(enumerator.Current) : null, s)); } } From 11e85601b7a0a68ea066aa64108a98ced5e648d6 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Thu, 3 Mar 2022 18:41:20 -0500 Subject: [PATCH 17/17] Make TypeScanner extension methods --- ...nerBuilderExtensions.cs => TypeScanner.cs} | 31 ++++--------------- .../StatorStartupExtensions.cs | 7 +++-- 2 files changed, 10 insertions(+), 28 deletions(-) rename src/StateR.Microsoft.Extensions.DependencyInjection/Internal/{TypeScannerBuilderExtensions.cs => TypeScanner.cs} (57%) diff --git a/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs b/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScanner.cs similarity index 57% rename from src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs rename to src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScanner.cs index d32f824..7a0801a 100644 --- a/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs +++ b/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScanner.cs @@ -4,35 +4,16 @@ namespace StateR.Internal; -//public static class TypeScannerBuilderExtensions -//{ -// public static IStatorBuilder ScanTypes(this IStatorBuilder builder) -// { -// var states = TypeScanner.FindStates(builder.All); -// builder.AddStates(states); - -// var actions = TypeScanner.FindActions(builder.All); -// builder.AddActions(actions); - -// var updaters = TypeScanner.FindUpdaters(builder.All); -// builder.AddUpdaters(updaters); - -// var actionHandlers = TypeScanner.FindMiddlewares(builder.All); -// builder.AddUpdaters(actionHandlers); - -// return builder; -// } -//} -public static class TypeScanner +public static class TypeScannerExtensions { - public static IEnumerable FindStates(IEnumerable types) + public static IEnumerable FindStates(this IEnumerable types) { var states = types .Where(type => !type.IsAbstract && type.IsSubclassOf(typeof(StateBase))); return states; } - public static IEnumerable FindInitialStates(IEnumerable types) + public static IEnumerable FindInitialStates(this IEnumerable types) { var initialStates = types .Where(type => !type.IsAbstract && type @@ -43,7 +24,7 @@ public static IEnumerable FindInitialStates(IEnumerable types) return initialStates; } - public static IEnumerable FindActions(IEnumerable types) + public static IEnumerable FindActions(this IEnumerable types) { var actions = types .Where(type => !type.IsAbstract && type @@ -54,7 +35,7 @@ public static IEnumerable FindActions(IEnumerable types) return actions; } - public static IEnumerable FindUpdaters(IEnumerable types) + public static IEnumerable FindUpdaters(this IEnumerable types) { var updaters = types .Where(type => !type.IsAbstract && type @@ -65,7 +46,7 @@ public static IEnumerable FindUpdaters(IEnumerable types) return updaters; } - public static IEnumerable FindMiddlewares(IEnumerable types) + public static IEnumerable FindActionFilters(this IEnumerable types) { var handlers = types .Where(type => !type.IsAbstract && type diff --git a/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs b/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs index 6a7d712..03a8b60 100644 --- a/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs +++ b/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs @@ -21,9 +21,10 @@ public static IStatorBuilder AddStateR(this IServiceCollection services) public static IStatorBuilder ScanAndAddStates(this IStatorBuilder builder, params Assembly[] assembliesToScan) { - var allTypes = assembliesToScan - .SelectMany(a => a.GetTypes()); - var initialStates = TypeScanner.FindInitialStates(allTypes); + var initialStates = assembliesToScan + .SelectMany(a => a.GetTypes()) + .FindInitialStates() + ; foreach (var initialState in initialStates) {