Skip to content

Fix decorator parameter selection for derived-service dependencies (#1459)#1484

Merged
tillig merged 5 commits into
developfrom
feature/issue-1459
Jun 15, 2026
Merged

Fix decorator parameter selection for derived-service dependencies (#1459)#1484
tillig merged 5 commits into
developfrom
feature/issue-1459

Conversation

@tillig

@tillig tillig commented Jun 15, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #1459. When a decorator's constructor takes a dependency typed as a more-derived service than the one being decorated (e.g. a sub-interface), Autofac would force-inject the decorated instance into that parameter even though the instance wasn't actually of that derived type — throwing InvalidCastException.

Root cause

The compatibleServiceParameter in DecoratorMiddleware.Execute (added for #1330) matched any decorator constructor parameter whose type was assignable-from the service type:

(pi, ctx) => serviceType.IsAssignableFrom(pi.ParameterType)

In the #1459 repro, D(IAb ab, IA a) decorates IA. Since IA.IsAssignableFrom(IAb) is true, the decorated A instance (only an IA, not an IAb) was injected into the IAb parameter, crashing.

Fix

Narrow the predicate so it only supplies the decorated instance to parameters the instance can actually be assigned to:

(pi, ctx) => serviceType.IsAssignableFrom(pi.ParameterType)
    && pi.ParameterType.IsInstanceOfType(currentInstance)

This is a strict narrowing of the match set: the #1330 behavior is preserved (where the decorated instance genuinely is the more-derived type), while parameters the instance can't satisfy fall through to normal autowiring (resolving the correct registration). Previously these cases only ever threw InvalidCastException, so no working behavior changes.

Tests

  • Closed-generic regression test for the exact The priority logic for selecting parameters in the decorator pattern is incorrect #1459 shape.
  • Open-generic counterpart (self-contained sub-interface).
  • Decorator-chain test covering the DecoratorContext.UpdateContext path (chained instance not assignable to the outer decorator's derived dependency).
  • Unregistered-dependency test asserting a clean DependencyResolutionException rather than a crash.

All decorator tests pass (76); full Autofac.Test suite passes (836). Each regression test was confirmed to fail against the pre-fix middleware.

Versioning

Bumped default.proj to 9.1.1 (PATCH) — a backwards-compatible bug fix; no public API change.

Note

This branch also carries an unrelated .pre-commit-config.yaml pin update (markdownlint) committed during the work to unblock local hooks.

tillig added 5 commits June 15, 2026 11:56
…1459)

The compatibleServiceParameter added for #1330 matched any decorator
constructor parameter whose type was assignable-from the service type
(serviceType.IsAssignableFrom(pi.ParameterType)). When a decorator took
a dependency typed as a more-derived service (e.g. a sub-interface of the
decorated service), that parameter matched the predicate and received the
decorated instance even though the instance was not actually of the more
derived type, throwing InvalidCastException.

Additionally require pi.ParameterType.IsInstanceOfType(currentInstance) so
the decorated instance is only injected where it is genuinely assignable;
otherwise the parameter falls through to normal autowiring. This narrows
the match set, preserving #1330 behavior.
Mirrors the closed-generic DecoratorTests case: an open-generic decorator
whose constructor takes a more-derived service dependency
(IDecoratedService<T>) than the decorated service (IService<T>) must
resolve that dependency from the container rather than receiving the
decorated instance. Fails with InvalidCastException against the pre-fix
middleware.
- Convert the closed-generic DerivedDependencyDecorator from a record to a
  plain class for consistency with the other decorator types in the file.
- Add a decorator-chain regression test exercising the DecoratorContext
  UpdateContext path: the outer decorator's more-derived dependency must
  still resolve from the container when the chained instance (inner
  decorator output) is not assignable to it. Fails with InvalidCastException
  against the pre-fix middleware.
- Add a test asserting a clean DependencyResolutionException when the
  more-derived dependency is unregistered (rather than InvalidCastException).
- Make the open-generic test self-contained with a purpose-built
  IDerivedService<T> sub-interface instead of reusing shared test infra.
@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77.78%. Comparing base (8f568b3) to head (54eb4e5).

Additional details and impacted files
@@           Coverage Diff            @@
##           develop    #1484   +/-   ##
========================================
  Coverage    77.77%   77.78%           
========================================
  Files          217      217           
  Lines         5827     5829    +2     
  Branches      1252     1253    +1     
========================================
+ Hits          4532     4534    +2     
  Misses         759      759           
  Partials       536      536           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.

The priority logic for selecting parameters in the decorator pattern is incorrect

1 participant