Summary
I am using C# with .NET 6 (or 8) trying to create basically a more-capable gRPC proxy service that can receive a gRPC request, wrap that gRPC inside of another one, and then forward that gRPC(gRPC) along to a different gRPC service that is equipped to handle it. It's all a bit weird I will admit.
What I've tried and the problems I've encountered
I've tried a couple of different approaches for this but I am currently trying to use a Middleware that will do the following:
- Intercept (not be confused with gRPC interceptors) the original gRPC
- Wrap the original gRPC in another gRPC
- Change the
context.Request.Pathto point to the service I really want it to be processed by - Call
await _next(context)and eventually ASP.NET will call the service I switched it out to
Unfortunately, by the time my Middleware is called the context.Features.Get<IEndpointFeature>() feature is set which points to the original gRPC service that I no longer want to process this request. Or if I don't define a matching service for the received gRPC, I get an IEndpointFeature as follows:
What I would like to do is change the IEndpointFeature.Endpoint to point to an Endpoint for my other gRPC service but I am not sure how to create one properly in my Middleware. I have looked at some of the gRPC for .NET source and it seems like all of the code I'd need is marked internal so I'd have to re-implement a whole bunch of logic for this myself. It seems like there should be an easier way to either create my own Endpoint instance or to get my Middleware to run before the built-in Middleware that is setting this IEndpointFeature in the first place.
Note that if I simply wanted to re-route the request as-is, that may be easier but I am actually needing to manipulate the gRPC request when it is received as well.
This is what I have in my Program.cs where you can see I don't explicitly enable any other routing middleware... I suspect the middleware that sets this IEndpointFeature may be part of the builder.Services.AddGrpc() call:
using service.Services;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// Additional configuration is required to successfully run gRPC on macOS.
// For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682
// Add services to the container.
builder.Services.AddGrpc();
WebApplication app = builder.Build();
app.UseMiddleware<MyRouteChangingMiddleware>();
// Configure the HTTP request pipeline.
app.MapGrpcService<GreeterServiceV1>(); // This is the 'original' gRPC service
app.MapGrpcService<GreeterServiceV2>(); // This is service I want to re-route to
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
app.Run();
ASP.NET Core Rewrite Middleware (update 10/26/2023): I found the RewriteMiddleware in the ASP.NET Core source which has the following comment/code in it that seems like it is eluding to the same problem I am encountering:
// An endpoint may have already been set. Since we're going to re-invoke the middleware pipeline we need to reset
// the endpoint and route values to ensure things are re-calculated.
context.SetEndpoint(endpoint: null);
var routeValuesFeature = context.Features.Get<IRouteValuesFeature>();
if (routeValuesFeature is not null)
{
routeValuesFeature.RouteValues = null!;
}
return _options.BranchedNext(context);
So I updated my own middleware to follow this example trying to re-invoke the middleware pipeline but unfortunately, after clearing the route and IEndpointFeature it does not seem to re-invoke whichever Microsoft middleware is responsible for setting those features and I end up with an "Unimplemented" response returned to my client.

I ended up figuring out a different way to redirect the incoming request to a different service endpoint without re-running the entire middleware pipeline. However, it still did not end up using this approach because I needed to also modify the gRPC service response in my middleware. Since I was calling
_next(), my understanding is that I should not be writing to the response stream myself. Instead, I need to use a terminal middleware that does not call_next()but instead writes the response itself.Still, I figure this may help others if their use-case is different so I'll share what I did to redirect the incoming gRPC request to a different endpoint than what it normally would've been sent to...
First, I used an extension method to inject my middleware (called TunnelingMiddleware) into my service container -- this is almost a direct copy from the RewriteMiddleware I mentioned in my question:
Notice this passes in the
IApplicationBuilder appto theTunnelingMiddlewareconstructor.My
TunnelingMiddlewareclass and constructor then looked like this:Then later in my InvokeAsync method: