Skip to content

Add real-world middleware examples and document the Type overloads - #201

Merged
tillig merged 1 commit into
mainfrom
feature/middleware-examples
Sep 1, 2026
Merged

tillig merged 1 commit into
mainfrom
feature/middleware-examples

Conversation

@tillig

@tillig tillig commented Sep 1, 2026 •

Copy link
Copy Markdown
Member

Fixes #160.

Why

The pipeline page explains the API thoroughly, but its only example was a "Hello World" that writes to the console. That leaves a reader knowing how to add middleware and not what to add it for — which is exactly what #160 asked for.

#160 also pointed at autofac/Autofac#1337 as a case solved with middleware where the solution "wasn't an obvious solution". Following that thread, the ask was an aspect applied to every registration without calling an extension method on each one — and that's the scenario that motivated the RegisterServiceMiddleware overloads added in 8.2.0. Those overloads turned out to be undocumented too, so this covers both.

What's added

Four examples, each a shape that comes up in practice rather than a demo:

  • Timing a resolve — wraps the activation phase. Uses NewInstanceActivated to avoid recording timings when a shared instance was handed back and nothing was actually constructed.
  • Substituting parameters — ParameterSelection is documented in the phase table as "the recommended point at which the resolve parameters should be replaced" but had no example. Shows ChangeParameters and appends to the existing set so an explicitly-passed parameter still wins.
  • Short-circuiting a resolve — set Instance, don't call next. The phase table says to use Sharing "to choose your own shared instance"; this is what that looks like.
  • Applying behavior across many services — the #1337 case. Notes that because service middleware attaches to the service, it runs whichever registration satisfies the request, including through decorators, where a registration-level hook would only see the innermost component.

Plus the three undocumented pieces of the 8.2.0 API: the Type-based overloads for when the service type isn't known at compile time, the descriptor that names your middleware in resolve traces instead of leaving it anonymous, and MiddlewareInsertionMode.StartOfPhase.

The sharp edge, called out

The short-circuit example carries a warning, because the trap isn't visible from the API. DisposalTrackingMiddleware runs in the registration pipeline (Phase => PipelinePhase.Activation, added to ComponentRegistration._lateBuildPipeline). An instance supplied from the service pipeline's Sharing phase never reaches it, so it isn't tracked for disposal and its lifetime events never fire. Easy to ship a leak on.

Verification

Every API in the new examples was checked against source rather than assumed:

  • All four IResolvePipelineBuilder.Use overloads confirmed in PipelineBuilderExtensions.cs, including the (phase, insertionMode, callback) form the timing example uses.
  • ChangeParameters, Instance, NewInstanceActivated, Service confirmed on ResolveRequestContext.
  • All nine RegisterServiceMiddleware overloads confirmed in ServiceMiddlewareRegistrationExtensions.cs; the Type and descriptor forms are exercised by RegistrationOnlyIfTests.
  • Each example pairs a service phase with service middleware and a registration phase with ConfigurePipeline, since the docs note that crossing them is an error.

sphinx -b html -W --keep-going and doc8 clean. I also checked the rendered heading tree: the four examples nest correctly as h3 under a new Examples h2, which confirms the hierarchy repair from #197 is holding.

Fixes #160.

The pipeline page explained the API thoroughly but its only example was a
"Hello World" that wrote to the console, which leaves the reader knowing how to
add middleware and not what to add it for. Four examples now cover the shapes
that actually come up: timing an activation, substituting resolve parameters,
short-circuiting to return a cached instance, and applying one behavior across
many services at once.

That last one is the case from autofac/Autofac#1337 - wanting an aspect applied
to every registration without calling an extension method on each one - which
is what motivated the RegisterServiceMiddleware overloads added in 8.2.0. Those
overloads were themselves undocumented: the Type-based ones for when the
service isn't known at compile time, the descriptor that names the middleware
in resolve traces instead of leaving it "anonymous", and StartOfPhase insertion.

The short-circuit example carries a warning, because the sharp edge isn't
obvious. DisposalTrackingMiddleware runs in the registration pipeline, so an
instance supplied from the service pipeline's Sharing phase is never tracked
for disposal and its lifetime events never fire.
@tillig
tillig force-pushed the feature/middleware-examples branch from 010ae9a to d13a88f Compare September 1, 2026 18:04
@tillig
tillig merged commit 5fd6fca into main Sep 1, 2026
2 checks passed
@tillig
tillig deleted the feature/middleware-examples branch September 1, 2026 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

More middleware examples

1 participant