Reputation: 5312
I was wondering whether there is a way to have Grpc .Net Core throw the original exception of the server side back to the client.
The problem is the following. When calling a grpc service, for example:
Client:
using (var channel = GrpcChannel.ForAddress("http://localhost:10042"))
{
var greeterService = channel.CreateGrpcService<IGreetService>();
var result = await greeterService.Greet();
Console.WriteLine(result.Greet);
}
Server:
public async Task<Greeting> Greet()
{
throw new ArgumentException();
}
Running this, the following RpcException is raised:
Grpc.Core.RpcException: 'Status(StatusCode=Unknown, Detail="Exception was thrown by handler.")'
Now, I would very much like to get this in a direction of actually raising an ArgumentException on the client side so this can be handled better.
What is possible, so I figured, is to do the following in my Startup.cs:
services.AddCodeFirstGrpc(options => options.EnableDetailedErrors = true);
Result:
Grpc.Core.RpcException: 'Status(StatusCode=Unknown, Detail="Exception was thrown by handler. ArgumentException: Value does not fall within the expected range.")'
That's better, but still not perfect.
My question is - Can I somehow raise the servers exception on the client side? Something I read up on was the fact that grpc allows "Interceptors" to intercept grpc calls. Is this a possibility to maybe do something here?
Upvotes: 8
Views: 5609
Reputation: 358
You can set up an Interceptor to catch server-side exceptions, serialize the exception metadata, and pack it into a custom RpcException
. The client can deserialize the metadata from the RpcException
and recreate the exception.
Here is a blog post I found that steps through the process: ASP gRPC Custom Error Handling
The downside, as you can imagine, is that this needs to be done for every exception. I've also been looking into the same question, and haven't found any straightforward ways to apply this to all exceptions.
Configure an interceptor:
services.AddGrpc(options =>
{
options.Interceptors.Add<GRPCInterceptor>();
});
Bare-bones interceptor:
public class GRPCInterceptor : Interceptor
{
private readonly ILogger logger;
public GRPCInterceptor(ILogger logger)
{
this.logger = logger;
}
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation)
{
logger.Debug($"starting call");
var response = await base.UnaryServerHandler(request, context, continuation);
logger.Debug($"finished call");
return response;
}
}
Upvotes: 6