Skip to main content

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
);
}
ParameterPurpose
actionThe action being dispatched
nextActionDelegate to continue the pipeline; call this to pass the action forward
Blocking Actions

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);
}
}

(StoreTests.TestMiddleware)

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
);

(Store.RegisterMiddleware)

Registration Timing

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​

ConceptDescription
MiddlewareIntercepts actions before reducers
Pipeline orderExecutes in registration order
Invoke patternCall nextAction to continue; omit to block
RegistrationAddMiddleware<T>() or store.RegisterMiddleware()
Transient lifetimeMiddleware is registered as transient and resolved when the store is created
Dispatch orderRuns 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