Prerequisites
Let’s create blazor wasm aspnet hosted app and confirm everything works.
We get following structured solution with Server and Client projects.
Protobuf vs json - the place of optimization
Our created project works with http client and sends requests using http protocol and json format of messages. It means every time when you send request it is serialized to json and deserelized on other side to message. We can’t avoid serialization deserialization steps, however grpc can impove the speed. Grpc uses protobuf message format, which is more lightweight than json. Although, protobuf format is not human readable, we can install extension like this one for our browser, which translates message from protobuf to json format.
Adding Grpc to the server
- Install packages
dotnet add package Grpc.AspNetCore
dotnet add package Grpc.AspNetCore.Web
-
Change
Startup.cs
public void ConfigureServices(IServiceCollection services) { services.AddGrpc(); }
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseGrpcWeb(); // should be between routing and enpoints
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<WeatherService>().EnableGrpcWeb();
endpoints.MapFallbackToFile("index.html");
});
}
-
Create
WeatherService .cs
underServices folder
-
Now we need to create contract which is located in
*.proto
file. Let’s add protos toShared
project and add toClient
andServer
projects this file as link in the following waynote
proto files should have following build action property
weather.proto
contains
syntax = "proto3";
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
package weather;
service WeatherForecasts {
rpc GetWeatherForecasts (google.protobuf.Empty) returns (GetWeatherForecastsResponse);
}
message GetWeatherForecastsResponse {
repeated WeatherForecast forecasts = 1;
}
message WeatherForecast {
google.protobuf.Timestamp date = 1;
int32 temperatureC = 2;
string summary = 3;
}
-
Edit
WeatherServic.cs
public class WeatherService : WeatherForecasts.WeatherForecastsBase { private static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "ld", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; public override Task<GetWeatherForecastsResponse> GetWeatherForecasts(Empty request, ServerCallContext context) { var rng = new Random(); var results = Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.UtcNow.AddDays(index).ToTimestamp(), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }).ToArray(); var response = new GetWeatherForecastsResponse(); response.Forecasts.AddRange(results); return Task.FromResult(response); } }
-
That’s all for server side.
Client side grpc client
-
Install packages
dotnet add package Google.Protobuf dotnet add package Grpc.Net.Client dotnet add package Grpc.Net.Client.Web dotnet add package Grpc.Tools
-
In the
FetchData.razor
page we have to injectGrpcChannel
instead ofHttpClient
and send requests using generated grpc client.@page "/fetchdata" @using Grpc.Net.Client @inject GrpcChannel Channel @using Weather @using Google.Protobuf.WellKnownTypes <h1>Weather forecast</h1> <p>This component demonstrates fetching data from the server.</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.ToDateTime().ToShortDateString()</td> <td>@forecast.TemperatureC</td> <td>@( 32 + (int)(forecast.TemperatureC / 0.5556))</td> <td>@forecast.Summary</td> </tr>} </tbody> </table>} @code { private IList<WeatherForecast> forecasts; protected override async Task OnInitializedAsync() { var client = new WeatherForecasts.WeatherForecastsClient(Channel); forecasts = (await client.GetWeatherForecastsAsync(new Empty())).Forecasts; } }
```
-
And last step, we should configure DI in
Program.cs
public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add<App>("app"); builder.Services.AddSingleton(services => { var backendUrl = builder.HostEnvironment.BaseAddress; var httpHandler = new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()); return GrpcChannel.ForAddress(backendUrl, new GrpcChannelOptions { HttpHandler = httpHandler }); }); await builder.Build().RunAsync(); }
Run application and press F12 to check that we reduce requests size.
Source code you can find on github
Conclusion
When you need good performance or want to reduce requests/responses size, that impoves speed of communication, Grpc can be good choice. However, we should know that it is just one point of different pros of the protocol.