Honor per-file ServiceProviderSource override in the CLI codegen paths (2.3.0)#401
Merged
jeremydmiller merged 2 commits intoMay 31, 2026
Merged
Conversation
…s (wolverine GH-2991) The runtime type loader (DynamicTypeLoader.Initialize/CompileAndAttach) applies a per-ICodeFile service-provider override via file.TryReplaceServiceProvider() -> IServiceVariableSource.ReplaceServiceProvider(), so e.g. a Wolverine HTTP endpoint configured with ServiceProviderSource.FromHttpContextRequestServices resolves its service-located dependencies from httpContext.RequestServices. The CLI codegen paths in DynamicCodeBuilder (write / preview / test) never applied that step, so `dotnet run -- codegen write` generated different (and wrong) code than the runtime: an opaque scoped lambda-factory dependency fell back to a created serviceScope while a sibling DbContext used httpContext.RequestServices — yielding two different scoped instances for one logical request. Fix: apply the override per file in WriteGeneratedCode / generateCode / TryBuildAndCompileAll via a shared applyServiceProviderOverride() helper. Regression guard: unlike the runtime path (transient IServiceVariableSource, fresh per file), the CLI reuses ONE shared source across all files, and ReplaceServiceProvider latches permanently (StartNewMethod only re-creates the default scope when it has not been replaced). So the helper first calls a new IServiceVariableSource.ResetServiceProvider() to undo any prior file's override — otherwise httpContext.RequestServices would leak into every following file (non-HTTP handlers, IsolatedAndScoped endpoints). New DynamicCodeBuilder test asserts the per-file reset+replace sequence isolates the override. Bumps to 2.2.8. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Fixes the root cause of wolverine#2991 / wolverine#2992: "HTTP endpoints resolve services wrong in codegen, but works in tests."
Root cause
The runtime type loader applies a per-
ICodeFileservice-provider override:So a Wolverine HTTP endpoint configured with
ServiceProviderSource.FromHttpContextRequestServicesresolves its service-located dependencies fromhttpContext.RequestServices.The CLI codegen paths in
DynamicCodeBuilder(WriteGeneratedCode/generateCode/TryBuildAndCompileAll) callgeneratedAssembly.GenerateCode(services)without that step. Sodotnet run -- codegen writeproduced different and wrong code than the runtime: an opaque scoped lambda-factory dependency fell back to a createdserviceScope, while a siblingDbContextusedhttpContext.RequestServices→ two different scoped instances for one logical request.Fix
Apply the override per file in all three CLI loops via a shared
applyServiceProviderOverride(file, services).The non-obvious part (regression guard)
The runtime path registers
IServiceVariableSourceas transient (fresh per file), so itsReplaceServiceProvideris naturally isolated. The CLI reuses one shared instance across every file, andServiceCollectionServerVariableSourcelatches_replacedServiceProvider = truepermanently (StartNewMethodonly re-creates the default scope while it is false). So naively applying the override in the loop would leakhttpContext.RequestServicesinto every subsequent file — non-HTTP message handlers,IsolatedAndScopedendpoints, etc.The helper therefore calls a new
IServiceVariableSource.ResetServiceProvider()(default no-op; implemented onServiceCollectionServerVariableSource) before each file, so each file's override is isolated.Tests
DynamicCodeBuilderTests.per_file_service_provider_override_is_isolated_to_its_own_file: an HTTP-style file (opts into the override) followed by a plain file, asserting the call sequencereset, replace, reset— i.e. the plain file does not inherit the override.CodegenTestspass on net9.0 + net10.0.Verified downstream
Built
WolverineWebApi(the #2992 repro) against this (local 2.2.8) and rancodegen write: the repro endpoint now resolves the opaque scoped service and theDbContextboth fromhttpContext.RequestServices, and no non-HTTPMessageHandlerfile referenceshttpContext.RequestServices(reset isolates correctly).Bumps
JasperFxVersionto 2.2.8.🤖 Generated with Claude Code