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 following table shows a comparison of features between gRPC and REST APIs with JSON format.
Explore the best and most comprehensive Blazor UI components library in the market.
Features | gRPC | REST APIs with JSON |
Contract | Required (.proto) | Optional |
Protocol | HTTP/2 | HTTP |
Payload | Protobuf (small, binary) | JSON (large, human readable) |
Prescriptiveness | Strict specification | Loose. Any HTTP is valid. |
Streaming | Client, server, bi-directional | Client, server |
Security | Transport (TLS) | Transport (TLS) |
Client code generation | Yes | OpenAPI + third-party tooling |
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 a 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.
Step 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
Every property of the Syncfusion Blazor components is completely documented for easy use.
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.
Step 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.
Explore the different application UIs developed using Syncfusion Blazor components.
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:
We can see that both pages return the same data, but one consumes a REST service, and the other consumes a gRPC service.
Test them all and inspect the network traffic to see the real advantage.
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:
- MVVM Pattern in Blazor for State Management—A Complete Guide
- CSV Data Sources in Blazor Pivot Table: A Groundbreaking Feature
- Build PWAs with Blazor WebAssembly Using Syncfusion Blazor Components
- Blazor Succintly
Comments (6)
‘… add gRPC services to the container’
???
which container? u lost me there
“Container” means the dependency injection container of the asp.net core application. It’s easier to do than to say, In the Startup.cs file, modify the ConfigureServices method by adding this row: “services.AddGrpc();”
Great article! I’d love to take advantage of this in Angular. Any tips?
Hi,
Sorry for delay in replying. GRPC can also be used in Angular but I am not the right person to give you suggestions on Angular: I don’t know it well enough.
Very helpful article, a few things that struggling to get my head around:
1. Adding JWT Bearer authentication to the call
2. Sending parameters in the request
Hi Andrew,
1. gRPC in Asp.net Core with Bearer token authentication is explained in this official documentation article https://docs.microsoft.com/en-us/aspnet/core/grpc/authn-and-authz?view=aspnetcore-5.0
2. If you want send parameters you can define a request messagge like this
message FooRequest {
string fooRequestParamOne = 1;
string fooRequestParamTwo = 2;
}
the service looks like this
service Foo {
rpc GetFoo (FooRequest) returns (FooResponse) {}
}
I hope I was helpful
Bye
Marco
Comments are closed.