Blazor WebAssembly: Debugging gRPC-Web with Custom Chrome Developer Tools

If you are working with Blazor, gRPC is a big issue for transferring data from APIs to clients. One issue of developing with gRPC-Web is debugging the transmitted data because the data is in an efficient binary message format. In this article, I will show you how to solve this problem with the help of my NuGet.

In this article:

Blazor WebAssembly: Debugging gRPC-Web with Custom Chrome Developer Tools
Patrick Jahr is architect at Thinktecture and focuses on Blazor WebAssembly, .NET Core and Angular.

Motivation

Since I’ve worked with Blazor WebAssemby, gRPC has also been a big topic in getting data from my APIs to my clients. But gRPC is currently not supported by browsers because it is impossible to implement the HTTP/2 gRPC spec in the browser(more here). Therefore, applications running in the browser need an alternative to use gRPC. The choice was gRPC-Web, a stripped-down version of gRPC. With gRPC-Web, sending data from the API to my client and back is possible. If you want to know more about gRPC and how gRPC-Web works in Blazor WebAssembly, check out the talk by my colleague Christian Weyer about “Blazor & gRPC – Code-first .NET SPA developer productivity.”

That means we can use gRPC-Web in the browser and everything is cool? No, unfortunately not.

One problem of developing with gRPC-Web is the debugging of the transferred data. Looking at the following image, we can see the Network tab of Chrome Browser Dev Tools.

grpc-response-protobuf

Here we see a gRPC-Web response that sent back a list of conferences to the client. But unfortunately, this is not readable because the messages are serialized with Protobuf, an efficient binary message format. But what can I do here to make the whole thing more readable?

For some time now, there has been a Chrome browser extension for the gRPC-Web Developer Tools. This extension makes it possible to show the request and response data in a pretty JSON format, as you see in the following picture in this article.
The only problem is that there is no way to use this extension in Blazor WebAssembly yet.

To solve this little problem, I have made it my task to make this handy tool also usable for Blazor WebAssembly 🙂

In this article, I will show you how to turn on the Developer Tools for gRPC-Web in your Blazor WebAssembly application using my Nuget package Thinktecture.Blazor.GrpcWeb.DevTools.

The GitHub repository for this article and also for the NuGet package can be found here.

What are the gRPC-Web Developer Tools, and what can they do?

The two main tasks of gRPC-Web Developer Tools are:

  • Collect and display all gRPC-Web network requests and responses in a list. 
  • Chrome Developer Tools extension for the official gRPC-Web library. It allows you to inspect the gRPC-Web network protocol in Chrome Developer Tools. The transferred data is displayed as a deserialized JSON object rather than in the original ProtoBuf format.

With the Developer Tools, you will get a new tab called gRPC-Web in your Chrome Developer Tools. This tool will list the configured gRPC-Web client network requests. The features are similar to the standard Network tab but only for gRPC-Web requests.

Selecting a network log entry displays the deserialized JSON for the request, response, and any error objects returned by your gRPC-Web server (See the following figure).

gRPC-Web Devloper Tools


Enable gRPC-Web Developer Tools for Blazor WebAssembly

To use the gRPC-Web Developer Tools in your project, you only have to add a few instructions to the Program.cs.
First, register the GrpcChannel class in the dependency injection. This is likely already present in your Blazor WebAssembly client code when you are using the gRPC-Web client.

				
					builder.Services.AddScoped(services =>
{
    var channel = GrpcChannel.ForAddress(backendUrl, 
        new GrpcChannelOptions 
        { 
            HttpHandler = new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()) 
        });
        
    return channel;
});
				
			

In the next step, the gRPC-Web service interfaces can be registered by using the AddGrpcService extension method from the blazor-grpc-web-devtooling Project.

With this extension method, the service is registered in the ServiceCollection with the help of the GrpcChannel and can then be added via DI to the component or a service (more will be explained in the next chapter).

				
					builder.Services.AddGrpcService<IConferencesService>();
builder.Services.AddGrpcService<ITimeService>();
				
			

Note: Of course, the services can also be registered and used without the Developer Tools.

To activate the developer tools, you have two options:
On the one hand, we can call the extension method EnableGrpcWebDevTools() in the Program.cs.

				
					builder.Services.EnableGrpcWebDevTools();
				
			
The second possibility is via appsetting.json. We can add the entry GrpcDevToolsEnabled to enable or disable the developer tools (see more information on this setting in the following section).
				
					{
  "GrpcDevToolsEnabled": true
}
				
			

Both options register an CallInvoker instance, which registers an additional interceptor.
This interceptor extends the base class Interceptor. The interceptor calls the JavaScript method postMessage with the help of JSInterop for each unary and server_streaming call (see more information on this interceptor in the following section).

The interceptor sends the request/response data via window.postMessage to the developer tools. The tools can receive and display the request or response, as seen in the following video snippet.

How it works under the hood

The project consists of three essential parts that work together to send the gRPC-Web requests and responses to the gRPC-Web Developer Tools.

The first part is an interceptor, which extends the base class Interceptor to get the possibility to override the gRPC-Web call methods and add its implementation or extend the existing implementation. The gRPC-Web Developer Tools currently support the two requests, Unary and ServerStreaming, which is overridden in the interceptor class GrpcMessageInterceptor. If one of the two methods is called, the base method is executed, and the request or response is also sent to the gRPC-Web Developer Tools with JSInterop.

				
					public partial class GrpcMessageInterceptor : Interceptor
{
    // ...

    public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
        TRequest request,
        ClientInterceptorContext<TRequest, TResponse> context,
        AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
    {
        var call = continuation(request, context);

        return new AsyncUnaryCall<TResponse>(
            HandleUnaryCall(context.Method.Name, request, call.ResponseAsync),
            call.ResponseHeadersAsync,
            call.GetStatus,
            call.GetTrailers,
            call.Dispose);
    }

    public override AsyncServerStreamingCall<TResponse> 
        AsyncServerStreamingCall<TRequest, TResponse>(
            TRequest request, 
            ClientInterceptorContext<TRequest, TResponse> context, 
            AsyncServerStreamingCallContinuation<TRequest, TResponse> continuation)
    {
        var streamingCall = base.AsyncServerStreamingCall(request, 
            context, continuation);

        var response = new AsyncServerStreamingCall<TResponse>(
                new AsyncStreamReaderWrapper<TResponse>(
                    streamingCall.ResponseStream, 
                    context.Method.Name, 
                    _jsRuntime),
                HandleServerStreamRequest(
                    streamingCall.ResponseHeadersAsync, 
                    request, 
                    context.Method.Name),
                streamingCall.GetStatus, 
                streamingCall.GetTrailers, 
                streamingCall.Dispose
            );

        return response;
    }
   // ....
}
				
			
And here we come to the second important point. With JSInterop, the postMessage-method is called, which sends the data to the gRPC-Web Developer Tools. Since this is a window method, no additional JavaScript file is needed here. The method can be called directly from the JSRuntime, as shown in the following code sample.
				
					internal static async Task HandleGrpcRequest<TRequest, TResponse>(
    this IJSRuntime jsRuntime, 
    string method,
    TRequest request, 
    TResponse response)
        {
            await jsRuntime.InvokeVoidAsync(
                "postMessage", 
                new GrpcDevToolsCall<TRequest, TResponse>(
                    GrpcWebDevToolsExtensionName, 
                    method, 
                    GrpcUnaryMethodName, 
                    request, 
                    response)
                );
        }
				
			

The JavaScript-Method window.postMessage() in the code above is called with the JSRuntime. This call will pass the data to the gRPC-Web Developer Tools. To let the Chrome Developer Tools know which tool to send the data to, the identifier for the gRPC-Web Developer Tools is also sent. The parameters method, GrpcUnaryMethodName, request, and response used by the gRPC-Web Developer Tools to display the requests and responses shown in the video of the previous chapter.

The last part is registering the Interceptor so that all calls will be sent to the gRPC-Web Developer Tools. To do this, I have written an extension method, which you can use to register the gRPC-Web services interfaces.

				
					public static void AddGrpcService<TService>(this IServiceCollection services)
            where TService : class
        {
            services.AddScoped(serviceProvider =>
            {
                var invoker = serviceProvider.GetService<CallInvoker>();
                if (invoker != null)
                    return GrpcClientFactory.CreateGrpcService<TService>(invoker);

                try
                {
                    var enabled = serviceProvider
                                    .GetRequiredService<IConfiguration>()?
                                    .GetValue<bool>(GrpcDevToolsSettingsKey);
                    if (enabled.HasValue && enabled.Value)
                    {
                        if (invoker == null)
                        {
                            var jsRuntime = serviceProvider.GetService<IJSRuntime>();
                            invoker = serviceProvider
                                    .GetService<GrpcChannel>()
                                    .Intercept(new GrpcMessageInterceptor(jsRuntime));
                        }
                        return GrpcClientFactory.CreateGrpcService<TService>(invoker);
                    }
                }
                catch (Exception e)
                {
                    var channel = serviceProvider.GetService<GrpcChannel>();
                    return GrpcClientFactory.CreateGrpcService<TService>(channel);
                }
            });
        }
				
			

In the code above, we see two options to enable the Developer Tools extension. On the one side, we look at the configuration, which we explained in the first part of this article. On the other side, the extension method EnableGrpcWebDevTools checks if the interceptor is already set.

And that’s it. With these three steps, we can use the gRPC-Web Developer Tools in our Blazor WebAssembly project.

Summary

With the help of a few lines of code, we have a much easier time finding errors, debugging network requests
or to better track the progress of requests in a gRPC-Web-enabled end-to-end application.
Feel free to use it. I am open to ideas and improvements and also pull requests 🙂

Free
Newsletter

Current articles, screencasts and interviews by our experts

Don’t miss any content on Angular, .NET Core, Blazor, Azure, and Kubernetes and sign up for our free monthly dev newsletter.

Related Articles
Angular
Managing Your Collections With the EntityAdapter: @ngrx/entity-Series – Part 1
This three-part series of blogposts is targeted at developers who have already gained experience with NgRx but still manage their collections themselves. In the first part I introduce the Entity Adapter, in the second part I show you how to connect it to NgRx and in the third part how to do it with the Component Store as well.
31.01.2023
Angular
Implementing Smart and Presentational Components with Angular: Condensed Angular Experiences – Part 4
In this article, we will explore how to apply the concept of smart and presentational components with Angular. We will choose a complex-enough target to see all aspects in action, yet understandable and within the scope of this article. The goal is to teach you how to use this architecture in your way. For that, we will iterate through different development stages, starting with the target selection and implementing it in a naive way. After the first development, we will refactor that naive solution into smart and presentational components that are reusable, refactor-friendly, and testable.
23.01.2023
Entity Framework
Entity Framework Core 7: N+1 Queries Problem
The N+1 queries problem has been our constant companion since day one of Entity Framework (Core). Entity Framework Core 2 (EF 2) introduced a new feature that caused the "N+1 queries problem" more often and was more difficult to detect, so it was removed in the following version. After a little back and forth, let's see how Entity Framework Core 7 (EF 7) handles this issue and why it will likely remain in the future.
09.01.2023