Proxoft.Redux.Hosting
5.1.0
dotnet add package Proxoft.Redux.Hosting --version 5.1.0
NuGet\Install-Package Proxoft.Redux.Hosting -Version 5.1.0
<PackageReference Include="Proxoft.Redux.Hosting" Version="5.1.0" />
paket add Proxoft.Redux.Hosting --version 5.1.0
#r "nuget: Proxoft.Redux.Hosting, 5.1.0"
// Install Proxoft.Redux.Hosting as a Cake Addin #addin nuget:?package=Proxoft.Redux.Hosting&version=5.1.0 // Install Proxoft.Redux.Hosting as a Cake Tool #tool nuget:?package=Proxoft.Redux.Hosting&version=5.1.0
Redux for .NET
Predictable state container for .NET C#. Inspired by ngrx library. This library is tightly connected to System.Reactive library.
The Redux is composed of logical components
- State - immutable state without any logic
- Action - a command or even which triggers changes on state
- Reducer - pure function which creates a new state based on action and current state
- Effects - serves as a side effect (e.g. reading/writing to database, using REST, etc.)
All of these are orchestrated by Store
Getting started
Define components
State
The state is an object containing all the application data. It may be of any type (class, record, struct, primitive). The important part about it is that the state should be immutable (or it must be used as it was immutable). That means: any time a state needs to be changed a new instance with modified data must be created.
public record ApplicationState
{
public string Message { get; init; } = "";
}
Actions
Actions are commands or events containing information (name of the action and optionally other data in the action) sent from your application to your store. They only need to implement the markup interface Redux.IAction.
public class SetMessageAction : IAction
{
public SetMessageAction(string message)
{
this.Message = message;
}
public string Message { get; }
}
public class TriggerAction : IAction
{
}
public class ResetMessageAction : IAction
{
}
Reducers
A reducer is a pure function with ((TState)state, (IAction)action) ⇒ (TState)state signature. The reducer transform current state into the next state according to action by creating a new instance with modified data. The pure function requirement implies that the reducer cannot habe any side-effect (e.g. persisting state, fetching data from another service). The reason is that the reducer must be predictable: it must always behave the same way and its behavior (result) depends only on input arguments.
public static class ApplicationReducer
{
public static ApplicationState Reduce(ApplicationState state, IAction action)
{
return action switch
{
SetMessageAction messageAction => state with { Message = messageAction.Message },
ResetMessageAction => state with { Message = "" },
_ => state
};
}
}
Effect
The Effect is a construct for all side-effects e.g. fetching data from REST, saving data to DB, execution of asynchronous tasks, etc. Effect may observe actions, state or both and execute corresponding actions. It also may dispatch new actions.
public class ApplicationEffect : Effect<ApplicationState>
{
private IObservable<IAction> Effect => this.ActionStream
.OfType<TriggerAction>()
.Select(_ => new SetMessageAction("Triggered SetMessage"))
;
}
Store
The Store<TState> wires it all together. It
- Holds application state of type TState.
- Executes reducers any time an action is dispatched via ActionDispatcher.
- Publishes update state
- Publishes actions and updated states to the effect
Store store = StoreHelper.Create(ApplicationReducer.Reduce, effects: new ApplicationEffect());
store.Initialize(new ApplicationState());
Use the store
Dispatch actions
store.Dispatcher.Dispatch(new TriggerAction());
store.Dispatcher.Dispatch(new ResetMessageAction());
Note: it is possible to register and inject own implementation of IActionDispatcher using DI. (see proxoft.redux.hosting package)
Subscribe to state changes
store.StateStream
.Select(state => {
Console.WriteLine(state.Message);
});
Builder
use the proxoft.redux.hosting package
services.AddRedux<ApplicationState>(
builder =>
{
builder
.UseScheduler(Scheduler.Default)
.UseJournaler<ActionJournaler>()
.UseExceptionHandler<DefaultExceptionHandler>()
.UseReducer(ApplicationReduce.Reduce)
.AddEffects(typeof(ApplicationState).Assembly);
});
Other features
In some scenarios it might be useful to suppress actions that have been dispatched, for example repetitive dispatch of an action or concurrent state changes causing dispatched action to become invalid.
public record ApplicationState
{
public bool ProcessRunning { get; init; }
public string LastMessage { get; init; } = "";
}
public class StartProcessAction : IAction
{
}
public class StopProcessAction : IAction
{
}
public class WarningAction(string message) : IAction
{
public string Message { get; } = message;
}
public static class ApplicationReducer
{
public static ApplicationState Reduce(ApplicationState state, IAction action)
{
return action switch
{
StartProcessAction => state with { ProcessRunning = true },
StopProcessAction => state with { ProcessRunning = false },
WarningAction a => state with { LastMessage = a.Message },
_ => state
};
}
}
public class ExternProcessRunner
{
private bool _started;
public void Start()
{
if (_started)
{
throw new Exception("cannot start");
}
_started = true;
}
public void Stop()
{
if (_started)
{
throw new Exception("cannot stop");
}
_started = false;
}
}
public class ProcessEffect(ExternProcessRunner runner) : Effect<ApplicationState>
{
private readonly ExternProcessRunner _runner = runner;
private IObservable<Unit> OnStart => this.ActionStream
.OfType<StartProcessAction>()
.Do(_ => _runner.Start())
.SelectVoid()
;
private IObservable<Unit> OnStop => this.ActionStream
.OfType<StopProcessAction>()
.Do(_ => _runner.Stop())
.SelectVoid()
;
}
public static class ActionGuard
{
public static IAction Validate(IAction action, ApplicationState state)
{
return action switch
{
StartProcessAction when state.ProcessRunning => new WarningAction("process already started"),
StopProcessAction when state.ProcessRunning == false => new WarningAction("process already stopped"),
_ => action
};
}
}
// ...
Store<ApplicationState> store = StoreHelper.Create<ApplicationState>(
ApplicationReducer.Reduce,
ActionGuard.Validate,
effects: new ProcessEffect(new ExternProcessRunner())
);
store.Initialize(new ApplicationState());
store.Dispatcher.Dispatch(new StartProcessAction());
store.Dispatcher.Dispatch(new StartProcessAction()); // will be changed to WarningAction
store.Dispatcher.Dispatch(new StopProcessAction());
store.Dispatcher.Dispatch(new StopProcessAction()); // will be changed to WarningAction
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net6.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 7.0.0)
- Proxoft.Redux.Core (>= 5.1.0)
-
net7.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 7.0.0)
- Proxoft.Redux.Core (>= 5.1.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
5.1.0 | 23,913 | 6/25/2024 |
5.0.1 | 7,511 | 11/7/2023 |
4.3.0 | 238 | 8/15/2023 |
4.2.0 | 175 | 5/25/2023 |
4.1.0 | 17,229 | 6/8/2022 |
4.0.3 | 564 | 5/18/2022 |
4.0.2 | 419 | 5/17/2022 |
4.0.1 | 417 | 5/17/2022 |
4.0.0 | 422 | 5/16/2022 |
3.1.2 | 921 | 3/4/2022 |
3.1.1 | 433 | 2/27/2022 |
3.1.0 | 407 | 2/27/2022 |
3.0.1 | 406 | 2/26/2022 |
3.0.0 | 425 | 2/26/2022 |
2.4.0 | 850 | 2/11/2022 |
2.3.0 | 470 | 10/28/2021 |
2.2.0 | 1,075 | 9/14/2021 |
2.1.1 | 463 | 7/19/2021 |
2.1.0 | 319 | 7/19/2021 |
2.0.0 | 323 | 7/12/2021 |
1.0.3 | 1,146 | 1/13/2021 |
1.0.2 | 333 | 1/13/2021 |