Skip to main content

grpc-web

grpc-web-architecture

There are two choices for how to add gRPC-Web to an ASP.NET Core app:

  • Support gRPC-Web alongside gRPC HTTP/2 in ASP.NET Core. This option uses middleware provided by the Grpc.AspNetCore.Web package.
  • Use the Envoy proxy's gRPC-Web support to translate gRPC-Web to gRPC HTTP/2. The translated call is then forwarded onto the ASP.NET Core app.

There are pros and cons to each approach. If an app's environment is already using Envoy as a proxy, it might make sense to also use Envoy to provide gRPC-Web support. For a basic solution for gRPC-Web that only requires ASP.NET Core, Grpc.AspNetCore.Web is a good choice.

h

gRPC-web currently supports 2 RPC modes:

  • Unary RPCs (example)
  • Server-side Streaming RPCs (example) !Client-side and Bi-directional streaming is not currently supported (see streaming roadmap).

To enable gRPC-Web with an ASP.NET Core gRPC service:

  • Add a reference to the Grpc.AspNetCore.Web package.
  • Configure the app to use gRPC-Web by adding UseGrpcWeb and EnableGrpcWeb
Title
   app.UseGrpcWeb(); // Must be added between UseRouting and UseEndpoints

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

Alternatively, the gRPC-Web middleware can be configured so that all services support gRPC-Web by default and EnableGrpcWeb isn't required. Specify new GrpcWebOptions { DefaultEnabled = true } when the middleware is added.

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

Consuming a gRPC-web

From a .net app

  • Add a reference to the Grpc.Net.Client.Web package.
  • Ensure the reference to Grpc.Net.Client package is version 2.29.0 or later.
  • Configure the channel to use the GrpcWebHandler:
Configure the channel to use the handler
var handler = new GrpcWebHandler(GrpcWebMode.GrpcWebText, new HttpClientHandler());
//var handler = new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler());


var channel = GrpcChannel.ForAddress("https://localhost:7226", new GrpcChannelOptions
{
HttpClient = new HttpClient(handler)
});

var client = new SpeakerServiceDefinition.SpeakerServiceDefinitionClient(channel);

  • The GrpcWebMode.GrpcWebText configures base64-encoded content. Required for server streaming calls in browsers. It will have Content-Type : application/grpc-web-text
  • The GrpcWebMode.GrpcWeb configures sending content without encoding. Default value. It will have Content-Type : application/grpc-web
Getting the data
Reading all the
try
{
// Get all gRPC - web
var speakers = client.GetAll(new Google.Protobuf.WellKnownTypes.Empty());
await foreach (SpeakerResponse response in speakers.ResponseStream.ReadAllAsync())
{
Console.WriteLine($"{response.Id}");
}
Console.WriteLine("Found speakers with gRPC-Web");

}
catch (Grpc.Core.RpcException e)
{
Console.WriteLine(e.Message);
}

Calling a gRPC-web from the Browser

  • Add a Blazor Web Assembly project
  • install: Grpc.Net.Client.Web
  • add a connected service reference pointing to the .proto file. This will end-up installing the grpc client factory
  • configure the gRPC client for the blazor app
Configure the gRPC client

var speakerAddress = "https://localhost:7226";
var speakers = new Uri(speakerAddress);

builder.Services
.AddGrpcClient<SpeakerServiceDefinition.SpeakerServiceDefinitionClient>
(o =>
{
o.Address = speakers;
})
.ConfigureChannel(o =>
{
o.HttpHandler = new GrpcWebHandler(GrpcWebMode.GrpcWebText, new HttpClientHandler());
});

  • inject the service in the fetchdata.razor
Title

@inject SpeakerServiceDefinition.SpeakerServiceDefinitionClient grpcClient


private List<SpeakerResponse> speakers = new List<SpeakerResponse>();

protected override async Task OnInitializedAsync()
{
using var call = grpcClient.GetAll(new Google.Protobuf.WellKnownTypes.Empty());
await foreach (SpeakerResponse response in call.ResponseStream.ReadAllAsync())
{
speakers.Add(response);
}

}


  • modify the table do display data:
Title
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>First Name</th>
<th>Last Name</th>
<th>Country</th>
</tr>
</thead>
<tbody>
@foreach (var speaker in speakers) {
<tr>
<td>@speaker.Id</td>
<td>@speaker.FirstName</td>
<td>@speaker.LastName</td>
<td>@speaker.Country</td>
</tr>
}
</tbody>
</table>
  • Test the services

CORS

To allow a browser app to make cross-origin gRPC-Web calls, set up CORS in ASP.NET Core. Use the built-in CORS support, and expose gRPC-specific headers with WithExposedHeaders.

Adding CORS
builder.Services.AddCors(o => o.AddPolicy("AllowAll", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.WithExposedHeaders("Grpc-Status", "Grpc-Message", "Grpc-Encoding", "Grpc-Accept-Encoding");
}));


Title

app.UseCors("AllowAll");

Practice

  • Install Grpc.AspNetCore.Web
Title
app.MapGet("/stream/speakers", async (ISpeakersService speakersService, IHttpContextAccessor accessor) =>
{
async IAsyncEnumerable<CountryModel> StreamSpeakersAsync()
{
var speakers = await speakersService.GetAllAsync();
foreach (var speaker in speakers)
{
await Task.Delay(500);
yield return speaker;
}
}
return StreamSpeakersAsync();
});