DEV Community

Cover image for 10 Steps to Replace REST Services with gRPC-Web in Blazor WebAssembly
Suresh Mohan for Syncfusion, Inc.

Posted on • Originally published at syncfusion.com on

10 Steps to Replace REST Services with gRPC-Web in Blazor WebAssembly

In modern web applications, the common way data is exchanged between client and server is through REST (representational state transfer) services. JSON is the standard file format for this two-way communication.

In REST, the client makes a request to a specific endpoint, and the server responds to it.

This architectural style works fine and has been the standard for several years. All web frameworks for creating SPA (single-page applications) are based on it. At first glance, even Blazor WebAssembly seems to use REST just like other frameworks.

Despite the wide use of this standard, there are some critical issues to consider:

  • If it is true that JSON is less verbose than XML, then it is also true that it is not optimized for bandwidth. Sometimes, JSON structure gets much too complicated.
  • REST APIs are stateless. This can have some negative consequences; for example, all requests should contain all the necessary information in order to perform the desired operations.
  • REST is not a protocol, but an architectural style. This involves increasing the responsibilities of developers. Most implementations of REST use only a subset of its principles but work, more or less. Often there is a confusion between REST services and RESTful services (those that implement REST services).

To be clear, REST architecture remains a great way to exchange data between clients and servers, but perhaps today we can afford to look beyond to find alternative solutions.

What are gRPC and gRPC-Web?

gRPC is a remote procedure call system created at Google in 2015. It is open source and can be defined as a replacement for Windows Communication Foundation (WCF). However, thanks to the latest update, it can also substitute REST API.

gRPC uses Protobuf (Protocol Buffers) as the format for the payload and supports all kinds of streaming:

  • Server-side streaming
  • Client-side streaming
  • Bidirectional streaming

From a performance point of view, Protobuf is an efficient binary message format. Protobuf serialization results in small message payloads, which is important in limited-bandwidth scenarios like mobile and web apps.

gRPC defines the contract of services and messages by the .proto file shared between the server and client. It allows you to automatically generate client libraries. gRPC is consistent across platforms and implementations.

The followingtable shows a comparison of features between gRPC and REST APIs with JSON format.

gRPC vs REST API

All of this sounds great, doesn’t it? Unfortunately, there’s bad news!

gRPC can’t be used from within web browsers because it requires HTTP/2 binary protocol. Don’t worry, the solution to this problem is called gRPC-Web, which makes gRPC usable in the browser. There’s also an implementation of gRPC-Web for .NET that has been officially released.

To be fair, gRPC-Web provides limited gRPC support. For example, client and bi-directional streaming aren’t supported, and there is limited support for server streaming too.

After this preamble, it’s time to go from theory to practice!

In this post I’ll show you how to consume a gRPC-Web service from within a Blazor WebAssembly app to build weather forecasting application. At the time of writing, there’s no native project template for this yet, so adding gRPC support to a Blazor WebAssembly app is a somewhat significant task. But again, don’t worry. No part of this is complicated. It’s only 10 small steps!

Step 1

Create a Blazor WebAssembly ASP.NET Core-hosted solution, and name it BlazorGrpc. Refer to the following screenshot.

Create a Blazor WebAssembly ASP.NET Core-hosted solution, and name it as BlazorGrpcStep 2

These are the changes that need to be made to the project files to add dependencies and more.

BlazorGrpc.Shared.csproj

<ItemGroup>
  <None Remove="weatherforecast.proto" />
</ItemGroup>

<ItemGroup>
  <PackageReference Include="Google.Protobuf" Version="3.13.0" />
  <PackageReference Include="Grpc.Net.Client" Version="2.31.0" />
  <PackageReference Include="Grpc.Tools" Version="2.31.0">
    <PrivateAssets>all</PrivateAssets>
    <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  </PackageReference>
</ItemGroup>

<ItemGroup>
  <Protobuf Include="weatherforecast.proto" />
</ItemGroup>

BlazorGrpc.Server.csproj

<PackageReference Include=”Grpc.AspNetCore” Version=”2.31.0” />
<PackageReference Include=”Grpc.AspNetCore.Web” Version=”2.31.0” />

BlazorGrpc.Client.csproj

<PackageReference Include="Grpc.Net.Client.Web" Version="2.31.0" />

Step 3

In the Shared project , create a weatherforecast.proto file like the code example below.

syntax = "proto3";

import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";

option csharp_namespace = "BlazorGrpc.Shared";

package WeatherForecast;

service WeatherForecastService {
  rpc GetWeatherForecast (google.protobuf.Empty) returns (WeatherForecastResponse);
}

message WeatherForecastResponse {
  repeated WeatherForecast forecasts = 1;
}

message WeatherForecast {
  google.protobuf.Timestamp dateTimeStamp = 1;
  int32 temperatureC = 2;
  string summary = 3;
}

The .proto file defines the service and everything necessary to run it correctly.

If you want to learn more about the syntax of this file, refer to this documentation:

https://developers.google.com/protocol-buffers/docs/proto3

Step 4

Go to the file properties and select the Protobuf compiler as the Build Action. A new window will appear thanks to the NuGet package Grpc.Tools. Then, select the Client and Server option as the gRPC Stub Classes. Refer to the following screenshot.

select the Protobuf compiler as the Build Action and select the Client and Server option as the gRPC Stub ClassesStep 5

Modify the WeatherForecast partial class with the following code snippets. Keep in mind that the main properties of this class are generated from the .proto file; however, you can also add some extra useful properties to this partial class.

using System;
using Google.Protobuf.WellKnownTypes;
namespace Blazorgrpc.Shared
{
    public partial class WeatherForecast
    {
        public DateTime Date
        {
            get => DateTimeStamp.ToDateTime();
            set { DateTimeStamp = Timestamp.FromDateTime(value.ToUniversalTime()); }
        }

        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    }
}

Step 6

In the Server project , add gRPC services to the container. In the Startup.cs file, modify the ConfigureServices method by adding this code:

services.AddGrpc();

To optimize the performance, I recommend taking advantage of response compression by adding the following code too:

services.AddResponseCompression(opts =>
            {
                opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
                    new[] { "application/octet-stream" });
            });

Step 7

Now, it’s time to implement the logic of our application in a service class, add it to the container, and also use it to modify the controller generated by the template. The service class will be something like the code below.

public class WeatherForecastService : 
 BlazorGrpc.Shared.WeatherForecastService.WeatherForecastServiceBase  

{
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        public override Task<WeatherForecastResponse> GetWeatherForecast(Empty request, ServerCallContext context)
        {
            var response = new WeatherForecastResponse();

            response.Forecasts.AddRange(GetWeatherForecast());

            return Task.FromResult<WeatherForecastResponse>(response);
        }

        public IEnumerable<WeatherForecast> GetWeatherForecast()
        {
            var rng = new Random();
            return Enumerable.Range(1, 365).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            });
        }
}

Note: The WeatherForecastService class is inherited from BlazorGrpc.Shared.WeatherForecastService.WeatherForecastServiceBase, which is generated automatically from the .proto file.

Step 8

Go back to the Startup.cs file to configure the gRPC-Web middleware in the Configure method as shown in the following code, so that all the services will support gRPC-Web by default.

app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true });

Then, add the route with the following code:

app.UseEndpoints(endpoints =>
      {
       endpoints.MapGrpcService<WeatherForecastService>();
      }

Step 9

Let’s move on to the client project. First, add the WeatherForecastService to the container, then create a gRPC-Web channel pointing to the back-end server and instantiate the gRPC clients for this channel.

Refer to the following code to modify the Program.cs file, to which we need to add additional required namespaces.

builder.Services. AddSingleton(services =>
            {
                var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
                var backendUrl = services.GetRequiredService<NavigationManager>().BaseUri;
                var channel = GrpcChannel.ForAddress(backendUrl, new GrpcChannelOptions { HttpClient = httpClient });

                return new WeatherForecastService.WeatherForecastServiceClient(channel);
});

Note: The WeatherForecastService.WeatherForecastServiceClient class is also generated automatically from the .proto file.

Step 10

Finally, add the FetchDataGrpc.razor file and make some small changes to the FetchData.razor and NavMenu.razor files. Don’t forget to make changes to the _Imports.razor file as well.

Refer to the following code.

@page "/fetchdatagrpc"
@inject WeatherForecastService.WeatherForecastServiceClient WeatherForecastServiceClient
@using Blazorgrpc.Shared
@using Google.Protobuf.WellKnownTypes
<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from the gRPC service.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {

    private IList<WeatherForecast> forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = (await WeatherForecastServiceClient.GetWeatherForecastAsync(new Empty())).Forecasts;
    }
}

That’s all folks! Now, we can have fun in testing our work. After implementing the above code examples, we will get a result like the following screenshot:

ResultWe can see that both pages return the same data, but one consumes a REST service, the other consumes a gRPC service.

Test them all and inspect the network traffic to see the real advantage.

Network Traffic Comparison
Network Traffic Comparison

In the screenshot above, you can see that the REST service has sent 55.6 KB, but the gRPC service has sent only 10.1 KB!

Resource

The converted project is available in this GitHub repository.

Conclusion

In this post, we have seen how to create a gRPC service in a Blazor WebAssembly-hosted application and how this solution brings significant performance benefits. This will be helpful in limited-bandwidth scenarios and will definitely speed up data transfer operations.

Try it out yourself and leave your feedback in the comments section below!

Syncfusion Essential Studio for Blazor offers 65+ high-performance, lightweight, and responsive UI components for the web, including file-format libraries, in a single package. Please take a look at our live demos in our sample browser for a hands-on experience.

For existing customers, the latest version is available for download from the License and Downloads page. If you are not yet a Syncfusion customer, you can try our 30-day free trial to check out the available features. Also, you can try our samples from this GitHub location.

You can also contact us through our support forums, Direct-Trac, or feedback portal. We are always happy to assist you!

If you like this post, we think you will also like the following:

Top comments (0)