C#/.NET gRPC service with Unary Methods

Service Definitions and Protobuf

Similar to WCF (Windows Communication Foundation), gRPC relies on service definitions to specify what methods and data types are exposed by the service. Service definitions in gRPC are written in protocol buffers (or protobuf for short) whereas in WCF, a WSDL is used.

An example of a protobuf service definition is shown below. It defines a service named “HelloService” which has a single method called “SayHello“. SayHello accepts a request parameter of type HelloRequest and returns a response of type HelloResponse

service HelloService {
  rpc SayHello (HelloRequest) returns (HelloResponse);
}

message HelloRequest {
  string greeting = 1;
}

message HelloResponse {
  string reply = 1;
}

Unary Methods

Unary methods are rpc (remote-procedure-call) calls wherein for every request, a response is sent back. It’s a lot similar to the normal function calls that we all write in our code except that for gRPC the calls go over the network.

In the above service definition, SayHello is a unary method.

Implementation

To create a gRPC project follow the steps in Your First gRPC Project. By default, Visual Studio creates a greet.proto file which contains the service definition of the generated GreeterService. The content of this greet.proto file is similar to the service definition a few paragraphs above.


Let’s create a second Unary method called GetCustomerByName by editing the greet.proto file

syntax = "proto3";

option csharp_namespace = "GrpcUnary";

package greet;

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

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

// The response message containing the greetings.
message GetCustomerResponse {
  repeated Customer customer = 1;
}

message Customer {
	string firstName = 1;
	string lastName = 2;
	int32 age = 3;
}

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

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

Press Ctrl-Shift-B to build the solution. If there are no errors in the proto file, the build should be successful.

Now that we have modified the service definition to include the GetCustomerByName method, we can go ahead and implement it in code. Open up GreeterService.cs and add an override for the method

public override async Task<GetCustomerResponse> GetCustomerByName(GetCustomerRequest request, ServerCallContext context)
{
    //Simulate a Db call to the Customers database
    var matched = _customers.Where(c => c.FirstName.StartsWith(request.NamePrefix));
    if (matched.Any())
    {
        var response = new GetCustomerResponse();
        response.Customers.AddRange(matched);
        return response;
    }

    throw new RpcException(new Status(StatusCode.NotFound, $"Unable to find a customer with a first name starting with {request.NamePrefix}"));
}


Press F5 to run the service and take note of the port where it is running


Test the method

Now that the service is running, let’s fire up FintX (https://github.com/namigop/FintX) to verify that the service is working fine. FintX is an open-source, native, and cross-platform gRPC client. I’m on Windows, so I have installed the windows package (macOS and Linux downloads are also available)

Click on the plus icon to add a client. The value entered in the http address should match the running service



Open GetCustomerByName method and edit the request. Double-click on the NamePrefix node to edit it then click the Run button


Give the NamePrefix some invalid value to verify that indeed an RPC exception is thrown with a NotFound status code


Others

The full source code of this tutorial can be downloaded from https://github.com/namigop/grpctutorials/tree/main/Unary. Happy coding!

Leave a Reply