Skip to main content

# grpc-web

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();
    });