How to Integrate gRPC Service in a Blazor Datagrid Application
Google’s open-source gRPC is an HTTP/2 based API framework for clients and servers to communicate with each other. It is gaining acceptance among API developers where inter-operability, streaming, and performance are key.
In this blog post, we will discuss the fundamentals of gRPC and how it compares to REST. We will finally look at integrating a gRPC service with a browser-based app built on Blazor.
Want to try out your own Blazor Datagrid Application? Download a Free Trial of ComponentOne!
gRPC Basics
Like any service-oriented architecture, gRPC defines a service with request and response interfaces called messages; these classes will have methods with parameters and return types that clients can call to send and receive data. An interface definition language (IDL) called protocol buffers is used to define the service. It is also used as the message interchange format by default. The interfaces are also available on the client and provide the same method as the server.
We all have seen the WeatherForecast service example in Visual Studio Blazor templates. Here is an example of a WeatherForecast protocol buffer:
syntax = "proto3";
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
option csharp_namespace = "FlexGridgRPC.Shared";
package WeatherForecast;
service WeatherForecasts {
rpc GetWeather (google.protobuf.Empty) returns (WeatherReply);
}
message WeatherReply {
repeated WeatherForecast forecasts = 1;
}
message WeatherForecast {
google.protobuf.Timestamp dateTimeStamp = 1;
int32 temperatureC = 2;
string summary = 3;
}
Let’s examine this code. The syntax is the version of ProtoBuff we are using; in this case, it is three. If this is not set, the compiler assumes it to be 2. Next are a couple of import statements, like we utilize “using” statements in C#. Namespace is self-explanatory.
Next is the “WeatherForecasts” service definition with the method “GetWeather” that has the parameter “Empty” and the return type “WeatherReply”. We then define what the WeatherReply is: a class with a collection of WeatherForecast. The number 1 depicts the order of properties in the class. When serializing the data, this is a performance tuning feature, and property name strings are replaced by their corresponding order number.
The “WeatherForecast” is defined next as a class containing three properties: dateTimeStamp, temperature, and summary. To learn more about field types, refer to the gRPC document.
In .NET, the compiler generates C# classes for the above service and message contracts described in the .proto file. This makes it easy to create and use the objects at both client and server.
gRPC vs. REST
There are several differences between gRPC and REST, some of them are:
- REST provides guidelines to build Web API’s over HTTP1 where as gRPC enforces messaging format between client and server over HTTP2
- REST uses JSON or XML format for data transfer. However, gRPC uses the binary format ProtoBuf. Protobuf serializes very fast, and the serialization results in small payloads, which is crucial for limited bandwidths.
- REST APIs need to create a new connection for each request; gRPC clients can send multiple requests over a single connection resulting in faster data transfer.
- No code generation is available for REST APIs for contracts, though tools are available.
- gRPC supports bidirectional streaming of data.
gRPC in Blazor Application
We will now walk through the steps to create a gRPC service and consume it in a Blazor application. Since Blazor is a browser-based application and not all browsers support HTTP/2, we would need to use gRPC web, which acts as a proxy between client and server to translate between HTTP/1 and HTTP/2.
Create an ASP.NET Core hosted Blazor Application using Visual Studio 2022.
Add the following NuGet packages to respective projects:
Client
- Grpc.Net.Client.Web
Server
- Grpc.AspNetCore
- Grpc.AspNetCore.Web
Shared
- Google.Protobuf
- Grpc.Net.Client
- Grpc.Tools
Adding gRPC Service
In the shared project, add a File, and name it weather.proto, copy the content of the weather.proto from the “gRPC Basics” section above. Make sure the build action for the weather.proto file is set to ‘Protobuf compiler,’ and gRPC Stub Classes is set to ‘Client and Server’. These actions ensure that relevant client and server files are generated by the tooling.
Next, add a partial class named WeatherForecast:
using System;
using Google.Protobuf.WellKnownTypes;
namespace FlexGridgRPC.Shared
{
public partial class WeatherForecast
{
// Properties for the underlying data are generated from the .proto file
// This partial class just adds some extra convenience properties
public DateTime Date
{
get => DateTimeStamp.ToDateTime();
set { DateTimeStamp = Timestamp.FromDateTime(value.ToUniversalTime()); }
}
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}
For convenience, this partial class has a Date, and TemperatureF properties since the generated properties in the C# class aren't the .NET date and time types. The properties use the Timestamp and Duration classes in the Google.Protobuf.WellKnownTypes namespace. This class provides methods for converting to .NET date and time types.
Exposing gRPC Service on the Server
In the Server project, delete the WeatherForecastController.cs file.
Next, we add the service that returns weather data. So, add a Services folder and add a C# file named WeatherForecastService. In the WeatherForecastService file, add below code:
using FlexGridgRPC.Shared;
using System;
using System.Linq;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
namespace FlexGridgRPC.Server.Services
{
public class WeatherForecastsService : WeatherForecasts.WeatherForecastsBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public override Task<WeatherReply> GetWeather(Empty request, ServerCallContext context)
{
var reply = new WeatherReply();
var rng = new Random();
reply.Forecasts.Add(Enumerable.Range(1, 30).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)],
}));
return Task.FromResult(reply);
}
}
}
Here, WeatherForecastsService inherits from WeatherForecasts.WeatherForecastsBase, which is automatically generated from the .proto file by the compiler.
Configuring gRPC on the Web
Next, we need to add gRPC to the app builder and enable the endpoint, hence adding the following code to the Program.cs file.
builder.Services.AddGrpc();
app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled=true});
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<WeatherForecastsService>().EnableGrpcWeb();
});
The Service is now ready. We will next configure the Blazor client to receive data and show the result.
Set-up Client Project
In the FlexGridgRPC.Client project, add the following packages from Nuget.org:
Next, we set up dependency injection in the client so that the WeatherForecastClient instance can be provided. This allows us to call the WeatherForecasts gRPC service anywhere in the client app. In the program.cs add the following code before awaiting builder.Build().RunAsync();
builder.Services.AddSingleton(services =>
{
var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
var baseUri = services.GetRequiredService<NavigationManager>().BaseUri;
var channel = GrpcChannel.ForAddress(baseUri, new GrpcChannelOptions { HttpClient = httpClient });
return new WeatherForecasts.WeatherForecastsClient(channel);
});
Now, open the Index.html file and add the following stylesheets between the <head></head> tags:
<link rel="stylesheet" href="_content/C1.Blazor.Core/styles.css" />
<link rel="stylesheet" href="_content/C1.Blazor.Grid/styles.css" />
<link rel="stylesheet" href="_content/C1.Blazor.Input/styles.css" />
<link rel="stylesheet" href="_content/C1.Blazor.DataPager/styles.css" />
<link rel="stylesheet" href="_content/C1.Blazor.DataFilter/styles.css" />
<link rel="stylesheet" href="_content/C1.Blazor.DateTimeEditors/styles.css" />
<link rel="stylesheet" href="_content/C1.Blazor.Accordion/styles.css" />
<link rel="stylesheet" href="_content/C1.Blazor.Calendar/styles.css" />
<link rel="stylesheet" href="_content/C1.Blazor.Menu/styles.css" />
In the <body></body> tag, add the following JS references:
<script src="_content/C1.Blazor.Calendar/scripts.js"></script>
<script src="_content/C1.Blazor.Core/scripts.js"></script>
<script src="_content/C1.Blazor.Input/scripts.js"></script>
<script src="_content/C1.Blazor.Grid/scripts.js"></script>
<script src="_content/C1.Blazor.Menu/scripts.js"></script>
Open the FetchData.razor page and add the following below the page directive
@inject WeatherForecasts.WeatherForecastsClient weatherforecastclient
@using FlexGridgRPC.Shared
@using Google.Protobuf.WellKnownTypes
@using C1.Blazor.Grid
Next, remove the <table> section and add a FlexGrid instead as below:
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<FlexGrid ItemsSource="@forecasts" Style="@("max-height:50vh; width:200vh")"></FlexGrid>
In the code section, use the weatherforecastclient object to get the list of Forecasts data; this forecast is bound to the FlexGrid.
@code {
private IList<WeatherForecast> forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = (await weatherforecastclient.GetWeatherAsync(new Empty())).Forecasts;
}
}
That is all. The app is ready to receive data using gRPC. Run the app to show weather data in a FlexGrid:
How this Blazor App benefitted from gRPC
Upon checking Network data transfer, we find that this application shows 900B size of data transfer for Weather service. A similar JSON service has 3.1Kb of data transfer, meaning that the gRPC payload is one-third of JSON. Also, the time taken was far less than the JSON payload.
Download the application.
Want to try out your own Blazor Datagrid Application? Download a Free Trial of ComponentOne!