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 diesem Artikel:

Blazor WebAssembly: Debugging gRPC-Web with Custom Chrome Developer Tools
Patrick Jahr ist Architekt bei Thinktecture. Sein Fokus liegt auf Backend-Systemen mit .NET Core und der Frontend-Entwicklung mit 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 🙂

Kostenloser
Newsletter

Aktuelle Artikel, Screencasts, Webinare und Interviews unserer Experten für Sie

Verpassen Sie keine Inhalte zu Angular, .NET Core, Blazor, Azure und Kubernetes und melden Sie sich zu unserem kostenlosen monatlichen Dev-Newsletter an.

Diese Artikel könnten Sie interessieren
.NET
Blazor WebAssembly in Practice: Maturity, Success Factors, Showstoppers

Blazor WebAssembly in Practice: Maturity, Success Factors, Showstoppers

ASP.NET Core Blazor is Microsoft's framework for implementing web-based applications, aimed at developers with knowledge of .NET and C#. It exists alongside other frameworks such as ASP.NET Core MVC. About two and a half years after the release of Blazor WebAssembly and based on our experiences from many customer projects at Thinktecture, we want to have a close look at the following questions: What is the current state of the framework? How can you successfully use Blazor? And where does it have limitations?
24.11.2022
.NET
Entity Framework Core: User-defined Fields and Tables

Entity Framework Core: User-defined Fields and Tables

The requirement to store additional fields, unknown at development time, in a relational database is not new. Nonetheless, none of the projects I know of are willing to change the database structure at runtime. What if there is a project which needs dynamically created fields and doesn't want or cannot use entity–attribute–value model or switch to No-SQL databases?
20.09.2022
.NET
Incremental Roslyn Source Generators in .NET 6: Adapt Code Generation Based on Project Dependencies – Part 5

Incremental Roslyn Source Generators in .NET 6: Adapt Code Generation Based on Project Dependencies – Part 5

The Roslyn Source Generator, implemented in the previous articles of the series, emits some C# code without looking at the dependencies of the current .NET (Core) project. In this article our DemoSourceGenerator should implement a JsonConverter, but only if the corresponding library (e.g. Newtonsoft.Json) is referenced by the project.
08.07.2022
Unterschiede
.NET
Blazor WebAssembly vs. Blazor Server – Welche Unterschiede gibt es und wann wähle ich was?

Blazor WebAssembly vs. Blazor Server – Welche Unterschiede gibt es und wann wähle ich was?

Das Blazor Framework von Microsoft gibt es inzwischen in drei "Geschmacksrichtungen". Die erste ist Blazor WebAssembly, die zweite Blazor Server, und zu guter Letzt gibt es noch Blazor Hybrid. In diesem Artikel wollen wir uns die zwei "echten", also Browser-basierten, Web-Anwendungs-Szenarien WebAssembly und Server anschauen.
04.07.2022
Three different textured walls
.NET
Dependency Injection Scopes in Blazor

Dependency Injection Scopes in Blazor

The dependency injection system is a big part of how modern ASP.NET Core works internally: It provides a flexible solution for developers to structure their projects, decouple their dependencies, and control the lifetimes of the components within an application. In Blazor - a new part of ASP.NET Core - however, the DI system feels a bit odd, and things seem to work a bit differently than expected. This article will explain why this is not only a feeling but indeed the case in the first place and how to handle the differences in order to not run into problems later on.
31.05.2022
.NET
Asynchrone Operationen: Blazor WebAssembly für Angular-Entwickler – Teil 5 [Screencast]

Asynchrone Operationen: Blazor WebAssembly für Angular-Entwickler – Teil 5 [Screencast]

Eine Webanwendung will natürlich auch mit Daten gefüttert werden. Doch diese müssen irgendwo her kommen. Nichts liegt näher als diese von einer Web API zu laden. Dieser Screencast zeigt, wie asynchrone Operationen in Blazor funktionieren und welche gravierenden Unterschiede es zu Angular gibt.
26.05.2022