Stator is a simple, DI-oriented, Redux-inspired or Model-View-Update (MVU) experiment using C# 9.0. It works well with Blazor and MobileBlazorBindings, including sharing states between the Xamarin part and the Blazor part of a hybrid mobile app. It should also work with any other .NET stateful client model.
As of 2021-04-08, the project does not support .NET Standard 2.0, only .NET 5+
This project uses:
- C# 9 record classes to ensure immutability and to simplify reducers.
- Dependency Injection to manage states (and everything else)
Name | NuGet.org | feedz.io |
---|---|---|
dotnet add package StateR |
||
dotnet add package StateR.Blazor |
||
dotnet add package StateR.Blazor.Experiments |
||
dotnet add package StateR.Experiments |
The following snippet represent a feature, surrounding a counter, where you can Increment
, Decrement
,
and Set
the value of the Count
property of that Counter.State
. The snippet also include the
InitialState
of that counter.
services
.AddStateR(appAssembly)
.AddAsyncOperations() // Add support for Redux thunk-like helpers
.AddReduxDevTools() // Add support for Redux DevTools
.Apply()
;
using StateR;
namespace BlazorMobileHybridExperiments.Features
{
public class Counter
{
public record State(int Count) : StateBase;
public class InitialState : IInitialState<State>
{
public State Value => new State(0);
}
public record Increment : IAction;
public record Decrement : IAction;
public class Reducers : IReducer<State, Increment>, IReducer<State, Decrement>
{
public State Reduce(State state, Increment action) => state with { Count = state.Count + 1 };
public State Reduce(State state, Decrement action) => state with { Count = state.Count - 1 };
}
}
}
Then from a Blazor component that inherits from StatorComponent
, we can dispatch those actions.
@page "/counter"
@inherits StateR.Blazor.StatorComponent
@inject IState<Features.Counter.State> CounterState
<h1>Counter</h1>
<p>Current count: @CounterState.Current.Count</p>
<button class="btn btn-primary" @onclick="@(async () => await DispatchAsync(new Features.Counter.Increment()))">+</button>
<button class="btn btn-primary" @onclick="@(async () => await DispatchAsync(new Features.Counter.Decrement()))">-</button>
It is not needed to inherit from
StatorComponent
, a component (or any class) can manually subscribe to anyIState<T>
.
I played around with a few other libraries and I was not 100% satisfied with how they did stuff. So while playing around with MobileBlazorBindings and the new hybrid apps, I found that C# 9 records were a great fit for this. I started experimenting with transforming immutable types (records) and ended up creating this project.
The name StateR
, pronounced Stator
, is inspired by MediatR
that was initially used under the hood to mediate the commands.
The stator is the stationary part of a rotary system [...]. Energy flows through a stator to or from the rotating component of the system.
Source: wikipedia
After hearing about it for years, I read about it and found the concept brilliant, experimented with it, and adopted the idea.
This library is based on the concepts introduced by Redux, but .NET is not JavaScript and .NET Core is built around Dependency Injection (DI), so I decided to take advantage of that.
There is no type and no real DI in JavaScript, so it make sense that the folks there did not take that into account when they built Redux.
I based the Redux DevTools implementation on Fluxor, which is a similar project, that I used once. That helped me lower the implementation time to connect Stator with Redux DevTools; thanks.
...
Please open an issue and be as clear as possible; see How to contribute? for more information.
If you would like to contribute to the project, first, thank you for your interest, and please read Contributing to ForEvolve open source projects for more information.
Also, please read the Contributor Covenant Code of Conduct that applies to all ForEvolve repositories.
-
dotnet test /p:CollectCoverage=true
=> coverage.json -
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura
=> coverage.cobertura.xml -
dotnet test --collect:"XPlat Code Coverage"
=> TestResults/{GUID}/coverage.cobertura.xml -
dotnet tool install -g dotnet-reportgenerator-globaltool
-
reportgenerator -reports:coverage.cobertura.xml -targetdir:coveragereport -reporttypes:Html
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura
reportgenerator "-reports:test\**\coverage.cobertura.xml" -targetdir:coveragereport -reporttypes:Html