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