Use gRPC in browser apps

By James Newton-King

Learn how to configure an existing ASP.NET Core gRPC service to be callable from browser apps, using the gRPC-Web protocol. gRPC-Web allows browser JavaScript and Blazor apps to call gRPC services. It's not possible to call an HTTP/2 gRPC service from a browser-based app. gRPC services hosted in ASP.NET Core can be configured to support gRPC-Web alongside HTTP/2 gRPC.

For instructions on adding a gRPC service to an existing ASP.NET Core app, see Add gRPC services to an ASP.NET Core app.

For instructions on creating a gRPC project, see Create a .NET Core gRPC client and server in ASP.NET Core.

gRPC-Web in ASP.NET Core vs. Envoy

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.

Configure gRPC-Web in ASP.NET Core

gRPC services hosted in ASP.NET Core can be configured to support gRPC-Web alongside HTTP/2 gRPC. gRPC-Web does not require any changes to services. The only modification is startup configuration.

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 to Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
    services.AddGrpc();
}

public void Configure(IApplicationBuilder app)
{
    app.UseRouting();

    app.UseGrpcWeb(); // Must be added between UseRouting and UseEndpoints

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

The preceding code:

  • Adds the gRPC-Web middleware, UseGrpcWeb, after routing and before endpoints.
  • Specifies the endpoints.MapGrpcService<GreeterService>() method supports gRPC-Web with EnableGrpcWeb.

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

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddGrpc();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseRouting();

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

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

Note

There is a known issue that causes gRPC-Web to fail when hosted by Http.sys in .NET Core 3.x.

A workaround to get gRPC-Web working on Http.sys is available here.

gRPC-Web and CORS

Browser security prevents a web page from making requests to a different domain than the one that served the web page. This restriction applies to making gRPC-Web calls with browser apps. For example, a browser app served by https://www.contoso.com is blocked from calling gRPC-Web services hosted on https://services.contoso.com. Cross Origin Resource Sharing (CORS) can be used to relax this restriction.

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.

public void ConfigureServices(IServiceCollection services)
{
    services.AddGrpc();

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

public void Configure(IApplicationBuilder app)
{
    app.UseRouting();

    app.UseGrpcWeb();
    app.UseCors();

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

The preceding code:

  • Calls AddCors to add CORS services and configures a CORS policy that exposes gRPC-specific headers.
  • Calls UseCors to add the CORS middleware after routing and before endpoints.
  • Specifies the endpoints.MapGrpcService<GreeterService>() method supports CORS with RequiresCors.

gRPC-Web and streaming

Traditional gRPC over HTTP/2 supports streaming in all directions. gRPC-Web offers limited support for streaming:

  • gRPC-Web browser clients don't support calling client streaming and bidirectional streaming methods.
  • ASP.NET Core gRPC services hosted on Azure App Service and IIS don't support bidirectional streaming.

When using gRPC-Web, we only recommend the use of unary methods and server streaming methods.

Call gRPC-Web from the browser

Browser apps can use gRPC-Web to call gRPC services. There are some requirements and limitations when calling gRPC services with gRPC-Web from the browser:

  • The server must have been configured to support gRPC-Web.
  • Client streaming and bidirectional streaming calls aren't supported. Server streaming is supported.
  • Calling gRPC services on a different domain requires CORS to be configured on the server.

JavaScript gRPC-Web client

There is a JavaScript gRPC-Web client. For instructions on how to use gRPC-Web from JavaScript, see write JavaScript client code with gRPC-Web.

Configure gRPC-Web with the .NET gRPC client

The .NET gRPC client can be configured to make gRPC-Web calls. This is useful for Blazor WebAssembly apps, which are hosted in the browser and have the same HTTP limitations of JavaScript code. Calling gRPC-Web with a .NET client is the same as HTTP/2 gRPC. The only modification is how the channel is created.

To use gRPC-Web:

  • Add a reference to the Grpc.Net.Client.Web package.
  • Ensure the reference to Grpc.Net.Client package is 2.29.0 or greater.
  • Configure the channel to use the GrpcWebHandler:
var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions
    {
        HttpHandler = new GrpcWebHandler(new HttpClientHandler())
    });

var client = new Greeter.GreeterClient(channel);
var response = await client.SayHelloAsync(new HelloRequest { Name = ".NET" });

The preceding code:

  • Configures a channel to use gRPC-Web.
  • Creates a client and makes a call using the channel.

GrpcWebHandler has the following configuration options:

  • InnerHandler: The underlying HttpMessageHandler that makes the gRPC HTTP request, for example, HttpClientHandler.
  • GrpcWebMode: An enumeration type that specifies whether the gRPC HTTP request Content-Type is application/grpc-web or application/grpc-web-text.
    • GrpcWebMode.GrpcWeb configures content to be sent without encoding. Default value.
    • GrpcWebMode.GrpcWebText configures content to be base64 encoded. Required for server streaming calls in browsers.
  • HttpVersion: HTTP protocol Version used to set HttpRequestMessage.Version on the underlying gRPC HTTP request. gRPC-Web doesn't require a specific version and doesn't override the default unless specified.

Important

Generated gRPC clients have sync and async methods for calling unary methods. For example, SayHello is sync and SayHelloAsync is async. Calling a sync method in a Blazor WebAssembly app will cause the app to become unresponsive. Async methods must always be used in Blazor WebAssembly.

Use gRPC client factory with gRPC-Web

A gRPC-Web compatible .NET client can be created using gRPC's integration with HttpClientFactory.

To use gRPC-Web with client factory:

  • Add package references to the project file for the following packages:
  • Register a gRPC client with dependency injection (DI) using the generic AddGrpcClient extension method. In a Blazor WebAssembly app, services are registered with DI in Program.cs.
  • Configure GrpcWebHandler using the ConfigurePrimaryHttpMessageHandler extension method.
builder.Services
    .AddGrpcClient<Greet.GreeterClient>((services, options) =>
    {
        options.Address = new Uri("https://localhost:5001");
    })
    .ConfigurePrimaryHttpMessageHandler(
        () => new GrpcWebHandler(GrpcWebMode.GrpcWebText, new HttpClientHandler()));

For more information, see gRPC client factory integration in .NET Core.

Additional resources