Middleware
Overview
Middleware forms a pipeline between Dispatch and reducers. Each middleware can inspect, transform, or block actions, and perform side operations like logging or analytics.
(IMiddleware)
What Is Middleware?
Middleware intercepts actions before they reach reducers. Unlike effects (which run after reducers), middleware runs during dispatch and can:
- Log actions - Record every action for debugging or analytics
- Transform actions - Modify or replace actions before reducers see them
- Block actions - Prevent actions from reaching reducers and effects
- Perform side operations - Execute operations like logging, analytics, or persistence (IMiddleware remarks, Store.CoreDispatch)
The IMiddleware Interface
All middleware implements IMiddleware:
public interface IMiddleware
{
void Invoke(
IAction action,
Action<IAction> nextAction
);
}
| Parameter | Purpose |
|---|---|
action | The action being dispatched |
nextAction | Delegate to continue the pipeline; call this to pass the action forward |
If you do not call nextAction, the action is blocked from reaching subsequent middleware and reducers.
(IMiddleware remarks)
Middleware Pipeline Order
Middleware executes in registration order. The store builds the pipeline in reverse so that the first registered middleware runs first:
(Store.BuildMiddlewarePipeline)
Creating Middleware
Implement IMiddleware and call nextAction to continue the pipeline:
// From Reservoir.L0Tests
private sealed class TestMiddleware : IMiddleware
{
private readonly Action onInvoke;
public TestMiddleware(
Action onInvoke
) =>
this.onInvoke = onInvoke;
public void Invoke(
IAction action,
Action<IAction> nextAction
)
{
onInvoke();
nextAction(action);
}
}
Ordered Middleware Example
To verify pipeline order, middleware can record its position:
// From Reservoir.L0Tests
private sealed class OrderedMiddleware : IMiddleware
{
private readonly int order;
private readonly List<int> orderList;
public OrderedMiddleware(
int order,
List<int> orderList
)
{
this.order = order;
this.orderList = orderList;
}
public void Invoke(
IAction action,
Action<IAction> nextAction
)
{
orderList.Add(order);
nextAction(action);
}
}
(StoreTests.OrderedMiddleware)
Registering Middleware
Register middleware using AddMiddleware<TMiddleware>():
public static IServiceCollection AddMiddleware<TMiddleware>(
this IServiceCollection services
)
where TMiddleware : class, IMiddleware;
(ReservoirRegistrations.AddMiddleware)
Middleware is registered as transient and resolved when the store is created. (ReservoirRegistrations.AddMiddleware, Store constructor)
Runtime Registration
You can also register middleware directly on the store after construction:
public void RegisterMiddleware(
IMiddleware middleware
);
Middleware registered after dispatch has started will be included in subsequent dispatches. Each Dispatch call rebuilds the pipeline.
(Store.Dispatch,
Store.BuildMiddlewarePipeline)
Testing Middleware
The store tests verify middleware behavior:
// From Reservoir.L0Tests
[Fact]
public void MiddlewarePipelineExecutesInOrder()
{
// Arrange
List<int> order = [];
sut.RegisterMiddleware(new OrderedMiddleware(1, order));
sut.RegisterMiddleware(new OrderedMiddleware(2, order));
sut.RegisterMiddleware(new OrderedMiddleware(3, order));
// Act
sut.Dispatch(new IncrementAction());
// Assert
Assert.Equal([1, 2, 3], order);
}
(StoreTests.MiddlewarePipelineExecutesInOrder)
// From Reservoir.L0Tests
[Fact]
public void RegisterMiddlewareAddsToDispatchPipeline()
{
// Arrange
bool middlewareInvoked = false;
sut.RegisterMiddleware(new TestMiddleware(() => middlewareInvoked = true));
// Act
sut.Dispatch(new IncrementAction());
// Assert
Assert.True(middlewareInvoked);
}
(StoreTests.RegisterMiddlewareAddsToDispatchPipeline)
Summary
| Concept | Description |
|---|---|
| Middleware | Intercepts actions before reducers |
| Pipeline order | Executes in registration order |
| Invoke pattern | Call nextAction to continue; omit to block |
| Registration | AddMiddleware<T>() or store.RegisterMiddleware() |
| Transient lifetime | Middleware is registered as transient and resolved when the store is created |
| Dispatch order | Runs before reducers and effects in the dispatch pipeline |
(ReservoirRegistrations.AddMiddleware, Store.CoreDispatch)
Next Steps
- Reservoir Overview - See where middleware sits in the pipeline
- Feature State - Learn how to define feature state that middleware can inspect
- Store - Understand the central hub that coordinates middleware, reducers, and effects