gRPC: Middleware Weaving, Validate Convention, User Exception Mapping, Bidi Streaming, Code-First Codegen, New Samples#2565
Open
erikshafer wants to merge 22 commits intoJasperFx:mainfrom
Conversation
…roto-first gRPC service chain codegen [WolverineBefore(MiddlewareScoping.Grpc)] and [WolverineAfter(MiddlewareScoping.Grpc)] methods on proto-first stubs are now woven into every generated gRPC service wrapper. Before-frames execute before bus dispatch; after-frames execute after (unary awaits the result so afters run before the return; server-streaming afters run after the IAsyncEnumerable loop drains). MessageHandlers-scoped methods are correctly excluded.
WolverineGrpcOptions.MapException<T>(StatusCode) lets callers override the built-in AIP-193 Exception → StatusCode table per exception type. User-registered mappings are consulted after the rich-status pipeline and before the default table. Inheritance is walked most-derived first; later registrations win for the same type. Resolves the last open item in Current Limitations from the gRPC docs.
…st stubs Static Validate/ValidateAsync methods returning Grpc.Core.Status? on a proto-first stub are woven into the generated wrapper. A non-null return throws RpcException before the Wolverine handler runs. Adds GrpcValidateShortCircuitFrame to GrpcServiceChain.AssembleTypes, a dedicated test proto + fixture (ValidateConventionFixture), and docs in handlers.md describing the convention, rules, and generated shape.
Wolverine now code-generates the bidi loop for proto-first services. Mark a stub with [WolverineGrpcService] and write a standard streaming handler (Handle(TRequest) → IAsyncEnumerable<TResponse>); the generated wrapper does the while/MoveNext outer loop automatically. Client-streaming (stream TRequest → TResponse) is still unsupported and still fails fast at startup with a clear error. Before-frames and the Validate short-circuit are not woven into bidi methods — they require a concrete TRequest before the loop begins.
…codegen Wolverine can now automatically generate concrete implementations for gRPC [ServiceContract] interfaces marked with [WolverineGrpcService]. This "Option C" workflow forwards interface methods directly to the Wolverine message bus: - Unary Task<TResponse> methods map to IMessageBus.InvokeAsync<T>() - Server-streaming IAsyncEnumerable<TResponse> methods map to IMessageBus.StreamAsync<T>() Includes support for protobuf-net.Grpc's CallContext and provides automatic validation to prevent conflicts between generated and hand-written implementations of the same contract.
Enables automatic discovery and registration of code-first gRPC service contracts. Wolverine now scans for interfaces decorated with both [ServiceContract] and [WolverineGrpcService], generates their implementations at startup, and maps them to the routing pipeline. Includes comprehensive integration tests for unary and server-streaming patterns, cancellation propagation, and diagnostic descriptions.
Adds two new sample project sets demonstrating Wolverine's code-first gRPC integration with protobuf-net.Grpc: - GreeterCodeFirstGrpc: A basic example of unary and server-streaming RPCs using shared interfaces and static handlers. - ProgressTrackerWithGrpc: A more advanced server-streaming example showing how to handle progress updates and client-side cancellation.
…ples Updates the gRPC documentation to cover the new generated code-first path where Wolverine implements ServiceContract interfaces automatically. - Adds a comprehensive comparison between code-first and proto-first styles. - Documents the three-pass discovery process (hand-written code-first, generated code-first, and proto-first). - Adds detailed walkthroughs for the GreeterCodeFirstGrpc and ProgressTrackerWithGrpc samples, including server-streaming cancellation patterns. - Updates the road map to reflect that the zero-boilerplate path now utilizes the full JasperFx codegen pipeline.
Renames the service implementation in the RacerWithGrpc sample for better clarity and consistency with other gRPC service naming conventions.
…tations Updates gRPC documentation and code comments to replace "zero-boilerplate codegen" and "Option C" phrasing with "generated implementation" for better clarity and consistency. Also simplifies technical descriptions regarding exception mapping and clarifies that middleware weaving is currently bypassed for bidirectional streaming methods.
…eware weaving Introduces `HandWrittenGrpcServiceChain` to generate thin delegation wrappers for concrete code-first gRPC services (classes ending in `GrpcService` that implement a `[ServiceContract]` interface). The generated wrapper implements the same interface, weaves `Validate`/before/after middleware (excluding bidi methods where no single request is in scope), then delegates to an `ActivatorUtilities`-resolved instance of the user's class. Supports all three RPC shapes: unary, server-streaming, and bidirectional streaming. Includes comprehensive discovery tests, integration tests via `HandWrittenChainFixture`, and validation short-circuit tests.
Extends `GrpcGraph.DiscoverServices` to accept `WolverineGrpcOptions` and apply registered middleware and `IChainPolicy` implementations to proto-first and hand-written gRPC chains after discovery.
…in types Changes the `AddMiddleware` signature from `Func<GrpcServiceChain, bool>?` to `Func<IChain, bool>?` to support filtering across proto-first (`GrpcServiceChain`) and hand-written (`HandWrittenGrpcServiceChain`) chains. Introduces a default `IsGrpcChain` predicate that matches both chain types, excluding code-first chains until they achieve full middleware parity (P3). Expands XML documentation to clarify filtering patterns and chain-type participation.
…during bootstrapping Introduces `IGrpcChainPolicy` to provide typed access to all three gRPC chain kinds (proto-first, code-first, hand-written) during discovery. Analogous to `IHttpPolicy` for HTTP endpoints. Policies are registered via `WolverineGrpcOptions.AddPolicy<T>()` or `AddPolicy(IGrpcChainPolicy)` and applied immediately before code generation. Includes `LambdaGrpcChainPolicy` for inline policy registration.
Adds comprehensive documentation for the three middleware/policy attachment patterns: - Inline `Validate`/`Before`/`After` methods on service classes (per-service) - `opts.AddMiddleware<T>()` for gRPC-scoped middleware with optional chain filtering - `opts.AddPolicy<T>()` / `IGrpcChainPolicy` for structural chain customization Includes API reference table entries and clarifies that global `Policies.AddMiddleware<T>()` intentionally does not reach gRPC chains.
… gRPC chains Extends `CodeFirstGrpcServiceChain` and `HandWrittenGrpcServiceChain` to fully participate in the middleware pipeline registered via `opts.AddMiddleware<T>()`. Frames are now cloned per method to preserve independent mutable state. Introduces `ModifyCodeFirstGrpcServiceChainAttribute` for per-interface customization, mirroring `ModifyGrpcServiceChainAttribute` for proto-first services. Updates documentation and API reference to reflect middleware support across all three chain kinds (proto-first, code-first generated, hand-written).
…ent transport breakage
Introduces `GrpcSerialTestsCollection` and applies `[Collection("GrpcSerialTests")]` to tests that manipulate `DynamicCodeBuilder.WithinCodegenCommand`, preventing parallel execution with transport compliance tests.
When the static flag is set during host startup, `applyMetadataOnlyModeIfDetected()` forces `DurabilityMode.MediatorOnly`, breaking transport operations like `SendAsync` and `RequireResponse`.
Replaces `host.Services.GetRequiredService<IMessageBus>()` with the cleaner `host.MessageBus()` extension method across all streaming handler acceptance tests. TL;DR: All 6 occurrences fixed. The root cause: we wrote the streaming tests resolving IMessageBus directly from the root IServiceProvider, but IMessageBus is registered as AddScoped<IMessageBus, MessageContext>() — ASP.NET's scope validation rejects this. The fix is host.MessageBus(), which creates a MessageBus (wrapping the singleton IWolverineRuntime) from the root provider — that's exactly what the existing host extension helpers do.
…n racer sample Removes `ComputeStandings` and `LogStandings` helper methods in favor of inline LINQ. Adds visual divider in client to separate each full standings snapshot and improves position display formatting.
…ncellation flow, and bidi caveats Simplifies verbose explanations in `streaming.md` around back-pressure, cancellation propagation, and server/bidi handler shapes. Adds tip explaining why `await Task.Yield()` appears in examples. Clarifies that bidi methods are skipped silently on the code-first generated-implementation path. In `contracts.md`, adds warning that bidirectional streaming is unsupported for generated implementations. In `handlers.md`, adds tip recommending domain exceptions over `RpcException` and condenses observability section to focus on natural `Activity.Current` propagation.
…ains Extends `CodeFirstGrpcServiceChain` to discover and weave static `Validate`/`Before`/`After` methods declared on the `[ServiceContract]` interface, following the same naming conventions and attributes as proto-first and hand-written chains. Introduces `DiscoveredBefores` and `DiscoveredAfters` properties that scan the service contract interface for qualifying static methods, filtered per-RPC-method by request type to prevent a `Validate(OrderRequest)` from firing inside an `InvoiceRequest` RPC. Adds `GrpcValidateShortCircuitFrame` to emit early-return logic when a `Validate` method produces a non-null `Status?`. Includes integration tests verifying `ICodeFirstValidatedService.Validate` short-circuits invalid requests and allows valid requests through to the handler.
…code-first gRPC chains Aligns code-first gRPC chains with Wolverine's handler idiom: static `Before`/`Validate`/`After` hooks now live on the handler class (the type containing `Handle`/`HandleAsync`/`Consume`/`ConsumeAsync` for the request message), not on the `[ServiceContract]` interface. Replaces `DiscoveredBefores`/`DiscoveredAfters` with `HandlerTypesFor(requestType)`, which scans `ApplicationAssemblies` (set by `GrpcGraph.DiscoverServices`) for handler types per RPC method. Assembly scanning is necessary because `AssembleTypes` fires before `HandlerGraph.Compile()` runs. Moves `ICodeFirstValidatedService.Validate` to `SubmitHandler.Validate` and updates tests to verify handler-based discovery instead of interface-based discovery.
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.
PR Summary — gRPC: Middleware Weaving, Validate Convention, User Exception Mapping, Bidi Streaming, Code-First Codegen, New Samples
Branch:
feature/grpc-middleware-weaving-validate-bidi-streamingBase:
main(post-#2525)What this PR does
This is a follow-up to #2525 that closes gaps in the Wolverine gRPC adapter, adds two new
end-to-end samples, and ships the first portion of the code-first codegen path (M9):
Middleware weaving for proto-first services —
[WolverineBefore]/[WolverineAfter]methods on a proto-first stub are now actually woven into the generated wrapper. This was the P0 correctness item from the post-gRPC Support for Wolverine HTTP Endpoints + IMessageBus.StreamAsync<T> #2525 roadmap:GrpcServiceChain.ScopingreturnedMiddlewareScoping.Grpcand the discovery infrastructure correctly filtered methods by scope, butAssembleTypesnever emitted the frames. A user who wrote a[WolverineBefore(MiddlewareScoping.Grpc)]method got silence rather than execution.User-configurable server-side exception mapping —
WolverineGrpcOptionsgainsMapException<T>(StatusCode)andMapException<T>(Func<T, StatusCode>), letting callers override the default AIP-193 table for specific exception types without swapping out the interceptor. The fluent API matches what Wolverine uses elsewhere (cf.WolverineOptions.Policies).Validate convention (
Status?short-circuit) for all three chain kinds — a staticValidate(TRequest) → Status?(orValidateAsyncreturningTask<Status?>) is woven into the generated wrapper before theIMessageBus.InvokeAsynccall. A non-null return throwsRpcExceptionimmediately; the Wolverine handler never runs. This now covers proto-first stubs, hand-written service classes, and code-first generated services. For code-first chains theValidatemethod lives on the Wolverine handler class (the class withHandle/HandleAsync) — the same idiom as the rest of the framework. Discovery uses assembly scanning rather thanHandlerGraphbecauseAssembleTypesfires beforeHandlerGraph.Compile()runs at startup.First-class bidirectional streaming for proto-first stubs — a
.protobidi RPC (stream TRequest → stream TResponse) is now code-generated rather than failing fast at startup. No newIMessageBusoverload was needed; the generated wrapper uses the same per-item bridge thatRacerWithGrpcalready demonstrates manually in code-first.Middleware weaving for code-first and hand-written chains —
AddWolverineGrpc(grpc => grpc.AddMiddleware<T>())now correctly applies to all three chain kinds (GrpcServiceChain,CodeFirstGrpcServiceChain,HandWrittenGrpcServiceChain). The filter predicate is also broadened so it accepts anyIGeneratedTyperather than onlyHandlerChain, closing the gap where middleware registered via the gRPC-scoped path was silently dropped for non-proto-first chains.IGrpcChainPolicyfor structural chain customization — a new interface alongsideAddMiddlewarefor concerns that go beyond frame weaving: inspecting service names, overriding idempotency style, or conditionally modifying chain configuration.Applyreceives all three chain kinds as typed lists, so policy implementations get full access to gRPC-specific properties without casting.Code-first codegen — zero-boilerplate interface path (M9) — applying
[WolverineGrpcService]to a[ServiceContract]interface now triggers Wolverine to generate a concrete service implementation at startup. No service class is written by hand. The generated class ({InterfaceNameWithoutLeadingI}GrpcHandler) injectsIMessageBus, implements the interface, and forwards each RPC toInvokeAsync<T>orStreamAsync<T>with the caller'sCancellationTokenextracted fromCallContextviaIVariableSource. This is the M9 sprint item previously listed as deferred.Design decisions worth calling out
Middleware discovery on
GrpcServiceChainThe middleware filter is
MiddlewarePolicy.FilterMethods<WolverineBeforeAttribute>applied toStubType.GetMethods()with scope guardMiddlewareScoping.Grpc. Methods are sorted alphabetically before frame emission so generated source is byte-stable across JIT runs (reflection order is not guaranteed). This mirrors howHandlerChainhandles middleware discovery.Before-frames that return
Status?are detected after theMethodCallis built (by inspectingcall.Creates) and automatically followed by aGrpcValidateShortCircuitFrame. This is how the Validate convention composes on top of the general middleware path — it's not special-cased at discovery time.Validate convention scoping
The hook covers all three chain kinds. The constraint — static method,
Status?return, lives on the handler class — follows the same shape as theValidateconvention elsewhere in the framework.For code-first chains, the method is discovered via assembly scanning:
CodeFirstGrpcServiceChain.HandlerTypesFor(requestType)walks the application assemblies looking for classes that own aHandle/HandleAsync/Consume/ConsumeAsyncmethod whose first parameter is the RPC request type. Those handler types are then scanned for[WolverineBefore]-eligible methods (viaMiddlewarePolicy.FilterMethods) in the same pass. Assembly scanning is used instead ofHandlerGraphbecauseAssembleTypesis called from the ASP.NET Core middleware pipeline, which runs beforeWolverineRuntime.StartAsync()compiles handler chains — reaching intoHandlerGraphat that point would always return null.Bidi streaming approach
The original roadmap (
.plans/next-pr-roadmap.md§PR-C) called for a newIMessageBus.StreamAsync<TRequest, TResponse>overload as the prerequisite for first-class bidi. This PR takes a different path: the generated wrapper usesIAsyncStreamReader<T>.MoveNext(ct)+Currentto loop the inbound stream and calls the existingBus.StreamAsync<TResponse>(request, ct)per item. The handler shape is identical to server streaming —Handle(TRequest) → IAsyncEnumerable<TResponse>— so no new handler convention is required and the existingRaceStreamHandlerpattern fromRacerWithGrpctranslates directly.The trade-off: this approach has per-item correlation semantics (one request → N responses, interleaved in arrival order). It is the wrong fit for a long-lived session where any inbound message can reorder or affect all pending responses. For that pattern, a saga + outbound messaging remains the right model. This is documented in
streaming.md.Before-frames are not woven into bidi methods. The
Validateshort-circuit and any[WolverineBefore]methods require a concreteTRequestinstance to be in scope when the generated method begins — which is not true for a bidi RPC whose first act is opening theIAsyncStreamReader. This is called out in the generated wrapper's XML doc, in the streaming docs, and in the Current Limitations list. Code-first services can still add per-stream validation via a manual shim.Pure client streaming (
stream TRequest → TResponse) is still not supported and still fails fast at startup with a clear error. OnlyBidirectionalStreaminghas been promoted;ClientStreamingremains in the unsupported list.WolverineGrpcOptions.MapException<T>Two overloads are exposed: a
StatusCodeconstant overload for the common case and aFunc<T, StatusCode>overload for when the mapping depends on the exception's properties (e.g. map aNotFoundExceptiontoNotFoundvs.Internalbased onex.IsTransient). Both register into aDictionary<Type, Func<Exception, StatusCode>>that the interceptor checks before falling back to the static AIP-193 table. Exact-type match only — no inheritance walk — which keeps the lookup O(1) and avoids surprising inheritance-based shadowing.Code-first codegen (M9) implementation
CodeFirstGrpcServiceChainimplementsICodeFileand is registered alongside the existing proto-first chains inGrpcGraph.BuildFiles(). Key design points:IVariableSourceforCancellationToken— rather than hardcodingcontext.CancellationTokenas a string in each generated frame, aCallContextCancellationTokenSourceis registered on eachGeneratedMethod. When any frame callschain.FindVariable(typeof(CancellationToken)), it resolves tocontext.CancellationTokenviaMemberAccessVariable. This is idiomatic JasperFx codegen rather than string interpolation.GeneratedAssembly.AddType(name, interface)— when the second argument is an interface, JasperFx'sGeneratedType.Implements()is called automatically, producing a class declaration that implements the interface and createsGeneratedMethodentries for each interface method. This was confirmed as a first-class API in JasperFx before committing to the approach.CodeFirstGrpcServiceChain.DiscoverMethodsinspects each interface method's return type:Task<T>→Unary,IAsyncEnumerable<T>→ServerStreaming. Methods not matching either shape are skipped with no error; this is intentional permissiveness so interfaces with non-RPC utility methods don't break discovery.ResolveTypeNamestrips a leadingIonly when followed by an uppercase letter (the conventional C# interface prefix).ImaginaryServiceContractkeeps itsI;ICodeFirstTestServicebecomesCodeFirstTestServiceGrpcHandler.AssertNoConcreteImplementationConflictsthrowsInvalidOperationExceptionat startup if a class implementing the interface also carries[WolverineGrpcService]. This prevents silent double-registration.GrpcGraph.FindCodeFirstServiceContractsrequires both[WolverineGrpcService]AND[ServiceContract]on the interface. Interfaces with only one attribute are not picked up. When the annotated interface lives in a shared contracts assembly, callers addopts.Discovery.IncludeAssembly(typeof(IMyService).Assembly)to include it in the Wolverine scan.New samples
Two new end-to-end samples join the existing six, both exercising the new code-first codegen path:
GreeterCodeFirstGrpc(port 5008)The canonical M9 showcase. The server project contains only handlers — no concrete service class.
IGreeterCodeFirstService(inMessages) carries both[ServiceContract]and[WolverineGrpcService];MapWolverineGrpcServices()discovers it and maps the generatedGreeterCodeFirstServiceGrpcHandler.The client uses
channel.CreateGrpcService<IGreeterCodeFirstService>()— same interface, both sides.Exercises unary (
Greet) and server-streaming (StreamGreetings).Closest grpc-dotnet equivalent: Coder — but the official example requires a hand-written concrete service class; this sample writes none.
ProgressTrackerWithGrpc(port 5009)A realistic server-streaming sample also built on the zero-boilerplate codegen path. The client
submits a
RunJobRequest; the handler yieldsJobProgressupdates as it simulates work withTask.Delay. The client demonstrates both the happy path (all steps complete) and mid-streamcancellation: cancel after step 3 →
RpcException(Cancelled)→ clean "Job cancelled" message.Closest grpc-dotnet equivalent: Progressor — but the handler has no knowledge of
IServerStreamWriter<T>or any gRPC type; it's a plainIAsyncEnumerable<T>method.Other changes
RacerWithGrpcsample cleanup —RaceStreamHandlerhad a privateLogStandingsmethod (12 lines of console formatting) and aComputeStandingsLINQ helper that obscured the bidi pattern. Both removed; standings computation is now inlined.RacerClient/Program.csgroups output by snapshot using a divider onposition.Position == 1.RacerWithGrpc/RacerServer/RacingService.cs→RacingGrpcService.cs— filename now matches the class name.WolverineGrpcServiceAttribute—AttributeTargetsexpanded fromClassonly toClass | Interfaceto support the new M9 interface-annotation path.What is NOT in this PR
WolverineGrpcServiceBasecodegen parity — the zero-boilerplate interface path is now shipped, but hand-writtenWolverineGrpcServiceBaseservices still resolve dependencies via service location. Generating per-method code files for hand-written service classes (so they benefit from Wolverine middleware and tighter DI) remains deferred.IMessageBus.StreamAsync<TRequest, TResponse>— not needed for the bidi approach taken here; remains a candidate for a later PR if a non-per-item-correlated bidi use case emerges.DiscoveredBefores/DiscoveredAftersfrom[WolverineBefore]interface-method attributes — the assembly scan discovers handler types by matchingHandle/HandleAsyncmethod signatures, then filters those types' methods for middleware hooks. Methods on the interface itself are not scanned for attributes (JasperFx pre-creates stubs for all interface methods, so static methods on the interface would produce 0-frame stubs that fail at codegen). This is intentional: the idiom is hooks on the handler, not on the contract.Tests
New test classes for M9 (code-first codegen):
code_first_codegen_integration_tests(9 tests) — round-trip unary, round-trip server streaming, cancellation propagation through unary, mid-stream cancellation, generated type name follows interface name convention, generated type implements the service contract interface, both RPC methods classified correctly, validate short-circuit throwsRpcExceptionbefore handler runs, validate returning null allows request through to handler.code_first_codegen_discovery_tests(8 tests) — finds annotated interfaces, returns only interfaces (not concrete classes),ResolveTypeNamestrips leadingIfrom conventional interface names, does not stripIwhen not followed by uppercase, conflict detection throws when concrete impl also has[WolverineGrpcService],Scopingproperty returnsMiddlewareScoping.Grpc,ApplicationAssembliesis null before discovery (confirms no-throw when chain is constructed directly),Validatemethod on handler class has the expected static shape.New test classes for coverage hardening (post-review):
wolverine_grpc_options_add_policy_tests(4 tests) —AddPolicy<T>()registers an instance,AddPolicy(instance)overload, returnsthisfor fluent chaining, multiple policies accumulate in registration order.igpc_chain_policy_discover_services_tests(2 tests) —GrpcGraph.DiscoverServicescalls every registeredIGrpcChainPolicy; the policyApplyreceives all three chain-type lists populated with the discovered chains.add_middleware_custom_filter_tests(2 tests) —AddMiddleware<T>(filter: _ => false)leaves every chain'sMiddlewareempty; a proto-first-only filter attaches toGrpcServiceChaininstances but not to code-first or hand-written chains.grpc_client_streaming_fail_fast_tests(1 test) — constructing aGrpcServiceChainfrom a stub that declares a client-streaming RPC throwsNotSupportedExceptionat startup with an actionable message naming the offending method.Pre-existing test classes (unchanged):
middleware_weaving_execution_tests— integration tests asserting that[WolverineBefore(MiddlewareScoping.Grpc)]methods actually execute during a live gRPC call, thatMiddlewareScoping.Http-only methods do not, and that multiple ordered before-frames run in alphabetical order.user_exception_mapping_tests—MapException<T>(StatusCode)andMapException<T>(Func<>)override integration tests; fallback behavior when no override is registered.grpc_validate_convention_tests— valid requests pass through, empty/blank/forbidden names short-circuit with the rightStatusCode, handler does not run when validate rejects.grpc_bidi_streaming_tests— single request yields N replies, multiple requests produce interleaved replies in order, zero requests produce zero replies, chain correctly classifies the bidi method.hand_written_grpc_chain_tests— delegation wrapper is generated, middleware is woven, inner service is resolved viaActivatorUtilities, conflict guard throws on double-annotation,Scopingproperty returnsMiddlewareScoping.Grpc(1 test added tohand_written_chain_discovery_tests).scope_discovery_tests/policy_leak_tests/type_name_disambiguation_tests—AddMiddlewarescoping, no leakage into messaging handler chains, type-name collisions between chain kinds are disambiguated.streaming_handler_support(existing, fixed) — tests were callinghost.Services.GetRequiredService<IMessageBus>()from root provider;IMessageBusis registered as scoped. Changed all 6 occurrences tohost.MessageBus()which wraps the singletonIWolverineRuntime.Sample verification scorecard (
dotnet runserver + client, checked against expected output):PingPongWithGrpcPingPongWithGrpcStreamingGreeterProtoFirstGrpcGreeterCodeFirstGrpcGreeterWithGrpcErrorsProgressTrackerWithGrpcOrderChainWithGrpcRacerWithGrpcDocs
Updated pages:
docs/guide/grpc/index.md— sample count updated to 8; both new samples added to the tip table;[WolverineGrpcService]API reference expanded to describe both the interface (codegen) and class (existing) uses; Roadmap updated to distinguish the shipped interface path from the still-deferredWolverineGrpcServiceBasecodegen parity.docs/guide/grpc/contracts.md— new top-level section "Code-first codegen (zero-boilerplate)" covering the interface annotation pattern,IncludeAssemblybootstrap, generated class naming, client-side usage, and the conflict guard warning. Existing code-first subsection headings renamed to "Code-first (hand-written service class)" to distinguish the two paths.docs/guide/grpc/handlers.md— "Discovery and codegen" updated from two passes to three, adding the new interface-based generated pass. Validate convention section added (proto-first only; rules, generated wrapper snippet, tip for field-level detail viagoogle.rpc.BadRequest).docs/guide/grpc/streaming.md— bidi is a first-class section with a generated wrapper example alongside the code-first manual bridge; gRPC-internals preamble trimmed; back-pressure paragraph replaced with a:::tipexplainingawait Task.Yield(); cancellation section simplified from a numbered list of gRPC internals to a single paragraph; Current Limitations updated; Related section updated to includeProgressTrackerWithGrpc.docs/guide/grpc/handlers.md— Middleware and Policies section added documenting all three registration paths (Validateinline,AddMiddleware<T>,IGrpcChainPolicy); "throw domain exceptions" promoted to a:::tipcallout; observability section simplified (removed ASP.NET Core Activity mechanics walk-through).docs/guide/grpc/contracts.md—:::warningadded for bidi being silently skipped on the generated-implementation path;:::warningadded for the[WolverineGrpcService]conflict rule (interface + class both annotated).docs/guide/grpc/samples.md— count updated to 8; both new samples added to the overview table with shape/style/grpc-dotnet comparisons; full detailed sections added forGreeterCodeFirstGrpcandProgressTrackerWithGrpc.