gRPC Services with Wolverine
INFO
The WolverineFx.Grpc package lets you expose Wolverine handlers as ASP.NET Core gRPC services with minimal wiring. It supports both the code-first (protobuf-net.Grpc) and proto-first (Grpc.Tools) styles.
Why gRPC?
If you're already using Wolverine to orchestrate message handlers, sagas, and HTTP endpoints, gRPC gives you another edge protocol for the same handlers. Benefits:
- Strongly-typed contracts shared across .NET and non-.NET services via
.protofiles, or code-first contracts that never leave C#. - Streaming first-class — plays naturally with Wolverine's
IMessageBus.StreamAsync<T>. - Wolverine handler reuse — the same handler can back a REST endpoint, an async message, and a gRPC call without duplication.
- Canonical error semantics — ordinary .NET exceptions thrown by a handler are mapped to the right gRPC
StatusCodeautomatically, following Google AIP-193.
What's in this section
Start here to get Wolverine's gRPC adapter running, then drill into the page that matches what you're building:
- How gRPC Handlers Work — the service →
IMessageBus→ handler flow, how it differs from HTTP and messaging handlers, and how OpenTelemetry traces survive the hop. - Code-First and Proto-First Contracts — the two contract styles side by side so you can pick (or mix) them.
- Error Handling — the default AIP-193 exception →
StatusCodetable plus the opt-ingoogle.rpc.Statuspipeline for rich, structured details. - Streaming — server streaming today, bidirectional via a manual bridge, and the shape of the cancellation contract.
- Typed gRPC Clients —
AddWolverineGrpcClient<T>(), the Wolverine-flavored wrapper overGrpc.Net.ClientFactorythat adds envelope-header propagation andRpcException→ typed-exception translation on the consuming side. - Samples — runnable end-to-end samples with pointers to the equivalent official grpc-dotnet examples for comparison.
Getting Started
Add the integration package and register it alongside the usual Wolverine bootstrap. AddWolverineGrpc does not register a gRPC host — callers decide whether they want code-first or proto-first (or both):
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseWolverine(opts =>
{
opts.ApplicationAssembly = typeof(Program).Assembly;
});
// Pick one (or both):
builder.Services.AddCodeFirstGrpc(); // protobuf-net.Grpc — code-first
builder.Services.AddGrpc(); // Grpc.AspNetCore — proto-first
// Wolverine's gRPC adapter (exception interceptor, discovery, codegen pipeline)
builder.Services.AddWolverineGrpc();
var app = builder.Build();
app.UseRouting();
// Discovers every '*GrpcService' / [WolverineGrpcService] type and maps it for you.
// Proto-first stubs generate a concrete wrapper on the fly.
app.MapWolverineGrpcServices();
app.Run();From here, How gRPC Handlers Work walks through what MapWolverineGrpcServices actually wires up and why a gRPC handler is just an ordinary Wolverine handler with a thin service shim on top.
Runnable Samples
Six end-to-end samples live under src/Samples/. Five are the classic trio shape (server, client, shared messages); OrderChainWithGrpc is a quartet because its proof-point is a chain between two Wolverine servers. dotnet run them side by side. See Samples for full walkthroughs and comparisons to the official grpc-dotnet examples.
| Sample | Shape | What to copy |
|---|---|---|
| PingPongWithGrpc | Code-first unary | [ServiceContract] + WolverineGrpcServiceBase forwarding to a plain handler |
| PingPongWithGrpcStreaming | Code-first server streaming | Handler returning IAsyncEnumerable<T>, forwarded via Bus.StreamAsync<T> |
| GreeterProtoFirstGrpc | Proto-first unary + server streaming + exception mapping | Abstract [WolverineGrpcService] stub subclassing a generated *Base + handlers |
| RacerWithGrpc | Code-first bidirectional streaming | Per-update bridge: client IAsyncEnumerable<TReq> → Bus.StreamAsync<TResp> for each item |
| GreeterWithGrpcErrors | Code-first rich error details | FluentValidation → BadRequest plus inline MapException → PreconditionFailure, with a client that unpacks both |
| OrderChainWithGrpc | Wolverine → Wolverine chain via AddWolverineGrpcClient<T>() | Typed client injected into a handler; envelope propagation + typed-exception round-trip with zero user plumbing |
API Reference
| Type / Member | Purpose |
|---|---|
AddWolverineGrpc() | Registers the interceptor, proto-first discovery graph, and codegen pipeline. |
MapWolverineGrpcServices() | Discovers and maps all gRPC services (code-first and proto-first). |
WolverineGrpcServiceBase | Optional base class exposing an IMessageBus property Bus. |
[WolverineGrpcService] | Opt-in marker for classes that don't match the GrpcService suffix. |
WolverineGrpcExceptionMapper.Map(ex) | The public mapping table — use directly in custom interceptors. |
WolverineGrpcExceptionInterceptor | The registered gRPC interceptor; exposed for diagnostics. |
opts.UseGrpcRichErrorDetails(...) | Opt-in google.rpc.Status pipeline — see Error Handling. |
opts.UseFluentValidationGrpcErrorDetails() | Bridge: ValidationException → BadRequest (from WolverineFx.FluentValidation.Grpc). |
IGrpcStatusDetailsProvider | Custom provider seam for building google.rpc.Status from an exception. |
IValidationFailureAdapter | Plug-in point for translating library-specific validation exceptions into BadRequest.FieldViolations. |
AddWolverineGrpcClient<T>() | Registers a Wolverine-flavored typed gRPC client — see Typed gRPC Clients. |
WolverineGrpcClientOptions | Named options for a typed client: Address, PropagateEnvelopeHeaders, MapRpcException. |
WolverineGrpcExceptionMapper.MapToException(rpc) | Inverse of Map — client-side RpcException → typed .NET exception table. |
Current Limitations
- Client streaming and bidirectional streaming have no out-of-the-box adapter path yet — there is no
IMessageBus.StreamAsync<TRequest, TResponse>overload, and proto-first stubs with these method shapes fail fast at startup with a clear error rather than silently skipping. In code-first you can still implement bidi manually in the service by bridging each incoming item throughBus.StreamAsync<TResp>(item, ct)— see Streaming for the pattern and the RacerWithGrpc sample. - Exception mapping of the canonical
Exception → StatusCodetable is not yet user-configurable on the server side (follow-up item). Rich, structured responses are already available — see Error Handling. On the client side,WolverineGrpcClientOptions.MapRpcExceptionalready allows per-client overrides — see Typed gRPC Clients. MiddlewareScoping.Grpcmiddleware — the enum value ships and is honored by Wolverine's discovery primitives, but no code path yet weaves[WolverineBefore(MiddlewareScoping.Grpc)]/[WolverineAfter(MiddlewareScoping.Grpc)]methods into the generated gRPC service wrappers. The attribute is safe to apply — it compiles, it is correctly filtered away from message-handler and HTTP chains, and it will start firing once the codegen path (tracked as M15) lands — but today nothing runs at RPC time. Until then, middleware that needs to execute on gRPC calls should live in a custom gRPC interceptor rather than rely on the attribute or onservices.AddWolverineGrpc(g => g.AddMiddleware<T>())(both take effect together in M15).
Roadmap
The gRPC integration has a handful of deferred items that are known-good fits but haven't shipped yet. They're listed here so contributors can plan around them and consumers know what's coming.
MiddlewareScoping.Grpccodegen weaving (M15) — attribute-based middleware on gRPC stubs (see Current Limitations above). Phase 0 landed the discovery + options surface; Phase 1 will wire execution into the generatedGrpcServiceChainwrappers.Validateconvention →Status?— HTTP handlers already support an opt-inValidatemethod whose non-null return short-circuits the call. The gRPC equivalent would returnGrpc.Core.Status?(or a richergoogle.rpc.Status) so a handler could express "this call is invalid, returnInvalidArgumentwith these field violations" without throwing. Deferred because it lands cleanest on top of the code-first codegen work below.- Code-first codegen parity — proto-first services flow through a generated
GrpcServiceChainwith the usual JasperFx codegen pipeline; code-first services (theWolverineGrpcServiceBasepath) currently resolve dependencies via service location inside each method. Generating per-method code files for code-first services — matching the HTTP and message handler story — is the prerequisite for theValidateconvention above and for tighter Lamar/MSDI optimization. - Hybrid handler shape (HTTP + gRPC + messaging on one type) — open design question. The hybrid HTTP/message handler pattern works today for two protocols; extending it to three raises naming and scoping questions (
MiddlewareScopingonly permits one value per[Middleware]attribute, and the handler method name conventions overlap). No concrete plan yet — feedback welcome on the tracking issue.

