This is your brain. This is your brain on computers.

Introduction to ASP.NET Core gRPC

Further Reading: If you’re lost with Visual Studio 2019, check out our guide. Learn how to set up a build and release pipeline for your new ASP.NET Core site with Azure Pipelines. Build out a suite of ASP.NET Core gRPC microservices and support them with .NET Core Worker Services. Your friends will thank you for the links!

What is gRPC?

gRPC is an acronym for general-purpose Remote Procedure Calls. Here is a definition straight from Google’s site:

gRPC is a modern open source high performance RPC framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.

There’s a lot of marketing speak wrapped up into that quote, so we’ll break it down a little. With gRPC, you’re given tools that allow you to make remote procedure calls between clients and servers or between servers in the case of microservice to microservice communication. If you’ve ever built traditional HTTP APIs like SOAP (XML) or REST (JSON/XML) services, then you’re already familiar with many of the concepts underlying the use cases for gRPC.

Maybe you’re building out a service that lets users manage their contacts and addresses. Or maybe your business has microservices to facilitate document generation and submission of those documents to third parties. The chances are high that these services expose an interface by which consumers can interact. That interface can be exposed in many ways, one of which is through the gRPC framework.

Do you remember WSDL files? If not, here’s why we are mentioning them. SOAP services can greatly accelerate business to business (B2B) data integrations by providing for a defined contract of services and data types to be used and exchanged between each party. That contract was defined in the WSDL. Developers could import the WSDL and have their development environments and toolkits generate many of the bindings necessary to make the interchange function. This is in contract to REST services that offer no rigid contract by which clients and servers should communicate. Instead, it’s up to each party to manually code to an agreement (or use optional and sparsely used frameworks like OpenAPI).

Like SOAP, gRPC offers similar contractual obligations and code generation capabilities. Instead of using XML, gRPC uses protocol buffers (Protobuf) to construct the message by which data is exchanged. Developers define their services and messages using the Protobuf format so that both ends of the communication understand which services are available, which parameters are required, how those parameters should be constructed, and what responses (if any) consumers should expect. Services and classes can then be automatically generated from Protobuf files through tooling.

But wait! There’s more!

FeaturegRPCTraditional HTTP (JSON)
ContractRequired via proto filesOptional (OpenAPI)
ProtocolHTTP/2HTTP
PayloadProtobuf (binary)JSON (human readable)
Formal SpecificationsStrictLoose (no codified rules)
Browser SupportNoYes

Here are the significant differences between gRPC and HTTP-JSON.

  • Required contract: gRPC uses Protobuf to define the exposed service endpoints and message formats whereas HTTP-JSON is the wild west unless both parties adhere to a standard like OpenAPI. Unfortunately, OpenAPI just isn’t very prevalent and is usually an afterthought by API developers.
  • HTTP/2: gRPC takes full advantage of many of the improvements offered by HTTP/2 such as binary transmission, multiplexing, header compression, and single connections.
  • Protobuf payload: As mentioned above, gRPC uses HTTP/2 binary transmission as opposed to HTTP-JSON’s textual transmission. With binary transmission, payload sizes are smaller and faster to process, but are no longer readable by humans in log files or browser dev tools.
  • Strict specs: Each end of the exchange are required to follow the strict specification defined by the Protobuf standard. With HTTP-JSON, no single person agrees about the right way to structure a JSON payload, which HTTP verbs to use, response codes, and URL structures. While that offers a lot of flexibility to developers, it often becomes a mess of differences when business are trying to connect their services together. gRPC removes that debate in favor of a cross platform standard.
  • Browser support: This is gRPC’s main weakness. Browsers don’t offer the level of control over the HTTP/2 features that gRPC requires. As such, you won’t be able to call a gRPC service directly from a browser without a lot of extra work through something like gRPC-Web. That’s why gRPC is usually reserved for communications between applications and servers in which a browser is not involved.

Further Reading: There’s a lot more to know beyond what we’ve briefly touched on here. Microsoft has a great article breaking down the major differences between gRPC and HTTP-JSON. Read on for more about performance, code generation, specifications, and advanced scenarios.

How Does It Fit With ASP.NET Core?

With the release of ASP.NET Core 3, you now have the ability to create an ASP.NET Core application that hosts gRPC services by using a built in project template. That means you can create your Protobuf files and services just like under any other framework and still get all of the great standard features included in the ASP.NET Core pipeline.

Dependency injection. The template comes with a default container using Microsoft’s dependency injection container which debuted with ASP.NET Core. While you can certainly use your own dependency injection container, sometimes it’s nice to have the official toolset used by the creators of the framework.

Configuration. The same configuration mechanisms that make ASP.NET Core flexible are available in gRPC Services. That means you can access appsettings.json, user secrets, environment variables, and command line arguments using the IConfiguration interface. This is a lot better than the app.config files of the full .NET Framework in which you are required to take a dependency on System.Configuration which definitely wasn’t cross-platform.

Logging. Like the configuration section, logging is also inherited from the ASP.NET Core paradigm. By default, Microsoft’s .NET Standard version of its logging framework gives you access to Console, Debug, EventSource, and EventLog (on Windows). With dependency injection and third party extensions, you can expand or create your own logging providers. It’s simple to start logging to files, databases, generic streams, or even cloud providers like Splunk or Elastic.

Set Up .NET Core SDK

Start by downloading the .NET Core SDK. As of this writing, the current version is 3.1.201. Microsoft regularly updates this, so make sure to check back and get the latest updates.

Microsoft website to download .NET Core SDK.

After download and installation, make sure it’s setup properly by opening your favorite command line tool (bash, mingw, powershell) and use the dotnet --version command to see which version is installed.

$ dotnet --version
3.1.201

Create Project

The ASP.NET Core gRPC template described in this article is not exclusive to Visual Studio 2019. You can either use the UI to create projects in Visual Studio 2019 or use the dotnet new grpc command from the .NET Core command line to create a project if you prefer using CLI + Visual Studio Code.

Further Reading: Not sure how to use Visual Studio 2019 but want to learn? Check out our ultimate guide for people in exactly your position!

Visual Studio 2019. Make sure you’re using the latest version (16.5.4) as of this writing. In the new project dialog, search for “gRPC” and choose that template to create the project.

Visual Studio Code. From the CLI, type dotnet new grpc -o MyGrpcService to create a new ASP.NET Core gRPC project in the MyGrpcService folder.

Regardless of how you create the project, this is what you should see in the case of success.

  • appsettings.json
  • appsettings.Development.json
  • MyGrpcService.csproj
  • Program.cs
  • Startup.cs
  • Protos folder
  • Services folder

Startup and Bootstrap Files

In Program.cs you’ll see the generic web host being bootstrapped and pointed to the startup class for service configurations and other dependency injection initializations. None of this is specific to gRPC. Check out Microsoft’s article about generic hosting for more information.

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    // Additional configuration is required to successfully run gRPC on macOS.
    // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682
    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

In Startup.cs you’ll see two methods. The first is responsible for adding any services that you need to the dependency injection container. The template automatically adds gRPC services by default. The second method sets up the HTTP request pipeline to use gRPC service routing. Note that any traditional HTTP requests will receive a response indicating that all gRPC requests must be made through a gRPC client.

// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
    services.AddGrpc();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseRouting();

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

        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
        });
    });
}

You should only edit this file if you need to 1) add your services to the DI container, 2) enable more features or custom middleware to the HTTP pipeline, or 3) map a new Protobuf defined gRPC service.

Protobuf and Services Files

gRPC is a contract-based paradigm. Developer of a service begins by constructing a Protobuf file to indicate which services and data types should be exposed and how those services and data types relate to each other. From those Protobuf files, server-side wire frames and client-side consumers can be constructed using the gRPC tooling.

In greet.proto you’ll see a simple method and a pair of request/response objects.

syntax = "proto3";

option csharp_namespace = "FunWithGrpc";

package greet;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply);
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings.
message HelloReply {
  string message = 1;
}

The Greeter service is defined as an rpc method named SayHello which takes in a parameter of type HelloRequest and returns a type of HelloReply. You can think of this as a method that you’re defining on a class.

The HelloRequest type has a single property of type string named name with a field number of 1 indicating that it is the first field in the message encoding. The HelloReply type has a single property of type string named message with a field number of 1. Read more about Protobuf field numbers here.

As we mentioned before, this a contract by which the client and server must adhere when communicating through the GreeterService. This defines the expectations and lets developers rest assured that there is a strict guideline of how to use the service.

Contrast this to an HTTP-JSON API where developers are wholly reliant on the documentation of the API designers. There is usually no exposed contract making requests and responses a guessing game.

In GreeterService.cs you’ll see service file which inherits from Greeter.GreeterBase. The GreeterBase class was automatically generated by the gRPC tooling based on the defined greet.proto contract file. A request to the SayHello endpoint with a payload of type HelloRequest will echo back to the client a type of HelloReply with a Message of Hello and the value included in the request object.

public class GreeterService : Greeter.GreeterBase
{
    private readonly ILogger<GreeterService> _logger;
    public GreeterService(ILogger<GreeterService> logger)
    {
        _logger = logger;
    }

    public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
    {
        return Task.FromResult(new HelloReply
        {
            Message = "Hello " + request.Name
        });
    }
}

This service has also taken advantage of dependency injection to get a reference to a logger component. You can use this type of DI paradigm to inject your own services to communicate with other APIs, databases, file systems, caching layers, and more.

Create Your Own Service

The previous section explained the files and layout created by the ASP.NET Core gRPC project template. Now you’re probably wondering how you can create your own service class from your own Protobuf files.

First, create a calculator.proto file in the Protos folder. Technically, you can create this file anywhere, but the standard is to use the Protos folder for organization improvements.

syntax = "proto3";

option csharp_namespace = "FunWithGrpc";

package calculator;

service Calculator {
  rpc AddIntegers (AddIntegerRequest) returns (AddIntegerReply);
}

message AddIntegerRequest {
  int32 parameter1 = 1;
  int32 parameter2 = 2;
}

message AddIntegerReply {
  int32 result = 1;
}

Here we’ve defined a Calculator service which takes in a AddIntegerRequest object and replies with a AddIntegerReply object. The request has two integer parameters, and the response has a single integer parameter.

By default, the gRPC tooling won’t know that you’ve added a new Protobuf file and thus the classes for your service implementation won’t be automatically generated. To make the tooling aware, edit the FunWithGrpc.csproj file to manually add a line to your Protobuf file.

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
    <Protobuf Include="Protos\calculator.proto" GrpcServices="Server" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Grpc.AspNetCore" Version="2.27.0" />
  </ItemGroup>
</Project>

The highlighted line above shows where we’ve added a reference to our new Protobuf file. Now when we build the project using dotnet build or Visual Studio 2019’s build process, the proper abstract base classes will be generated from this contract.

Try it out. Build your project and then check in the obj folder for the generated base class file. It should be named something like CalculatorGrpc.cs. Don’t worry too much about the contents of this file since most of it is automatically generated and not meant to be read or maintained by people. The important thing is that you should see an abstract class with a method that matches the signature you created in the Protobuf file.

/// <summary>Base class for server-side implementations of Calculator</summary>
[grpc::BindServiceMethod(typeof(Calculator), "BindService")]
public abstract partial class CalculatorBase
{
  public virtual global::System.Threading.Tasks.Task<global::FunWithGrpc.AddIntegerReply> AddIntegers(global::FunWithGrpc.AddIntegerRequest request, grpc::ServerCallContext context)
  {
    throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, ""));
  }
}

Create a new CalculatorService.cs class in the Services folder. Inherit from CalculatorBase and override the AddIntegers method with your own implementation. Based on the name of the method, the intention of it is to obviously add the two request parameters together and return the result.

public class CalculatorService : Calculator.CalculatorBase
{
    private readonly ILogger<CalculatorService> _logger;
    public CalculatorService(ILogger<CalculatorService> logger)
    {
        _logger = logger;
    }

    public override Task<AddIntegerReply> AddIntegers(AddIntegerRequest request, ServerCallContext context)
    {
        return Task.FromResult(new AddIntegerReply
        {
            Result = request.Parameter1 + request.Parameter2
        });
    }
}

In our implementation above, we are overriding the expected method and doing nothing more than adding the requests parameters into the response. Obviously, you can make your services more complicated by using other libraries that you’ve injected through the DI container as described previously.

Finally, in Startup.cs you’ll need to map your new service to the application’s endpoints collection. See the highlighted line below.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseRouting();

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

        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
        });
    });
}

IIS Server Limitations

According to the official documentation and this GitHub issue, you cannot currently host an ASP.NET Core gRPC application in Azure App Services or using IIS in your on premise infrastructures. The short version is that the HTTP/2 implementation in HTTP.sys does not support HTTP response trailing headers on which gRPC relies.

There’s two separate problems here. First, IIS won’t work because it relies on HTTP.sys which doesn’t have what gRPC needs. Second, Azure App Services won’t work even if you use a Linux or custom container because App Services terminate TLS connections prior to passing data along to the App Service. That TLS termination relies on the same HTTP.sys implementation which prevents IIS from functioning.

The suggested workaround mentioned in the above GitHub issue is to use Kestrel directly in a VM or AKS by using Kestrel behind a reverse proxy. While this can work for some situations, it’s essentially a shift from platform-as-a-service (PaaS) to infrastructure-as-a-service (IaaS) which has major implications for larger applications and smaller teams. You’ll become responsible for a host of new options and maintenance just to get gRPC functionality.

Justin Skiles

Justin Skiles

Justin has been developing enterprise application software for over 10 years primarily using Microsoft stacks, Azure, and various open source tools. He has most recently been trying his best as a Manager and Director of Software Engineering in the health care industry.

Share the Knowledge

Share on facebook
Facebook
Share on twitter
Twitter
Share on linkedin
LinkedIn
Share on pinterest
Pinterest
Share on facebook
Share on twitter
Share on linkedin
Share on pinterest

Follow our updates

JOIN OUR SUBSCRIBERS

GET FREE UPDATES

Keep Exploring. Choose Your Path.

Be a Better, Smarter You

With our in depth guides, you’re bound to be setup for success.

Our experts have been collectively developing software for over 20 years.

We find the best tools and direct you to them so that you don’t have to.

JOIN THE CONVERSATION

Get the latest ultimate guides, tutorials, and advice to level up your skills.