Repository navigation
Container validation, lifetime helpers, and reflection-free adapter chains - #4
Merged
Merged
Conversation
Three of the most common dependency injection mistakes now have an answer in the library instead of a runtime surprise: - ValidateRelayRegistrations() statically analyses an IServiceCollection and reports captive dependencies, missing registrations, singletons capturing disposable transients and duplicate registrations - all of them at once, before the provider is built. Keyed registrations, open generics, IEnumerable<T>, Lazy<T> and optional constructor parameters are understood. - IRelayScopeRunner runs work in a fresh scope, so a singleton no longer has to capture a scoped service to get its job done. - AddLazyResolution() enables Lazy<T> for any registered service, which defers expensive construction and breaks constructor cycles. The decorator API grows Decorate<TInterface, TDecorator>(), TryDecorate and DecorateKeyed. Decorating now wraps every non-keyed registration rather than only the last one, and keyed descriptors are no longer matched by the non-keyed overloads - reading ImplementationType on one of those threw. AddRelayServices() became idempotent; calling it twice used to leave duplicate IRelayContext and IRelayResolver descriptors behind.
…safe Chains used to resolve the Adapt method through MethodInfo.Invoke on every step, and typed chains built their registration with MakeGenericType plus Activator.CreateInstance. The builders already know the source, target and adapter types statically, so the invoker is now closed over at registration time: executing a chain performs no reflection at all. Steps constructed by hand still fall back to the old path. With that gone the package is marked IsAotCompatible/IsTrimmable and fully annotated. The entry points that genuinely cannot be trimmed - assembly scanning and RelayTo(Func<IRelayContext, Type>) - are annotated with RequiresUnreferencedCode rather than suppressed. Also fixed along the way: - assembly scanning no longer dies with ReflectionTypeLoadException when a single type in the assembly fails to load - failover reports every relay failure through an AggregateException, and the parallel strategy surfaces all failures instead of just the first - MultiRelay takes a TimeProvider (resolved from the container when present), so retry backoff is deterministic in tests - RelayToAll/RelayToAllWithResults accept a CancellationToken - adapters shared between chain steps are registered once - DefaultRelayContext falls back to DOTNET_ENVIRONMENT for non-ASP.NET hosts Packaging: packages go to artifacts/packages, GeneratePackageOnBuild is off so local builds stay fast, and ContinuousIntegrationBuild only applies on CI. Adds a release workflow (tag or manual dispatch) and widens CI to a three-OS matrix that also packs on every run. Version 2.0.0: IMultiRelay<T> signatures changed and Decorate wraps every registration now.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This branch finishes the .NET 10 work and takes the library from "fluent registration helpers" to something that also tells you when your container is wrong.
Container validation
ValidateRelayRegistrations()reads the descriptors and reports what is broken before a provider is ever built:It catches captive dependencies, missing registrations, singletons holding disposable transients and (opt-in) duplicates, and it understands keyed registrations, open generics,
IEnumerable<T>,Lazy<T>and optional constructor parameters. UnlikeValidateOnBuild/ValidateScopesit reports everything at once and never has to resolve anything.Lifetime helpers
IRelayScopeRunner- run a unit of work in a fresh scope from a singleton, which is the actual fix for "cannot consume scoped service from singleton". Registered byAddRelayServices().AddLazyResolution()-Lazy<T>for any registered service: defers expensive construction, breaks constructor cycles, no service locator.Decorators
Decorate<TInterface, TDecorator>(),TryDecorateandDecorateKeyed. Two behaviour changes here: decorating now wraps every non-keyed registration instead of only the last one, and the non-keyed overloads no longer match keyed descriptors - doing so used to throw, because readingImplementationTypeon a keyed descriptor is invalid.Reflection-free chains, trimming and AOT
Adapter chains used
MethodInfo.Invokeper step andMakeGenericType+Activatorfor typed chain registration. The builders know all the types statically, so the invoker is now built at registration time and execution does no reflection at all. Hand-built steps keep the old path.That made it possible to mark the package
IsAotCompatible/IsTrimmablewith real annotations. Assembly scanning andRelayTo(Func<IRelayContext, Type>)carry[RequiresUnreferencedCode]instead of a suppression, so the analyzer tells the truth.Fixes
ReflectionTypeLoadExceptionAggregateException; the parallel strategy no longer hides all but the firstAddRelayServices()is idempotentDefaultRelayContexthonoursDOTNET_ENVIRONMENTMultiRelaytakes aTimeProvider, so retry backoff is testableBuild and CI
Packages go to
artifacts/packages,GeneratePackageOnBuildis off,ContinuousIntegrationBuildonly applies on CI. Newrelease.ymlpublishes on av*tag or manual dispatch (needs theNUGET_API_KEYsecret); CI now runs on Linux, Windows and macOS and packs on every build.Breaking changes (2.0.0)
IMultiRelay<T>.RelayToAll/RelayToAllWithResultstake an optionalCancellationToken. Call sites are unaffected; custom implementations need updating.Decorate<TInterface>(...)wraps every non-keyed registration and skips keyed ones.324 tests pass, all 18 examples run, build is clean with warnings-as-errors and the trim/AOT analyzers enabled.