Blazor&Dragons! How to consume gRPC-web from Blazor – part 2: the server
Blazor&Dragons! How to consume gRPC-web from Blazor – part 2: the server

Blazor&Dragons! How to consume gRPC-web from Blazor – part 2: the server

2020, Jun 26    

Hi All! Last time we gave a look at the client, today we’re going to focus on the backend and see how we can write a gRPC server that can be called from a Blazor webassembly application.

Did I mention how nerd I was? Yeah, probably I did. Just have a look at what I’m listening right now while writing this article:

</figure>

So, the server. As I wrote last time, it is basically just a simple proxy over the D&D REST API. I deliberately decided to not add any caching or other fancy things, just to focus on the transport.

The first thing to do is to define the shape of our messages:

syntax = "proto3";
import "google/protobuf/empty.proto";
option csharp_namespace = "BlazorAndDragons.Server";

package classes;

service Classes {
  rpc GetAll (google.protobuf.Empty) returns (GetAllResponse);
  rpc GetDetails(GetDetailsRequest) returns (GetDetailsResponse);
}

message GetAllResponse {
    message ClassArchiveItem{
     string id = 1;
     string name = 2;
	} 

    repeated ClassArchiveItem data = 1;
}

message GetDetailsRequest{
    string id = 1;
}

message GetDetailsResponse{
    string id=1;
    string name=2;
    int32 hitDie=3;
    repeated Proficiency proficiencies=4;

    message Proficiency{
	    string name=1;
	}
}

This is basically the same thing as the one on the client. The only difference is the namespace: option csharp_namespace = “BlazorAndDragons.Server”;

The next step is to create the Typed HTTP Client that will fetch the data from the D&D API:

public class DnDClient : IDnDClient
{
        private readonly HttpClient _httpClient;

        public DnDClient(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }

        public Task<DndArchive<DnDClassArchiveItem>> GetAllClassesAsync() =>
            _httpClient.GetFromJsonAsync<DndArchive<DnDClassArchiveItem>>("classes");

        public Task<DndClass> GetClassAsync(string id) =>
            _httpClient.GetFromJsonAsync<DndClass>($"classes/{id}");
}

Defining an interface is useful for a humongous number of reasons. TDD anyone?

All the nice cross-cutting concerns can be defined elsewhere, for example at IoC registration. We could decide to add improved logging, caching, circuit breaker and so on. I didn’t because I’m lazy as…well, you know.

Now that we have an HTTP Client, all we have to do is inject it into our gRPC service and use it:

public class ClassesService : Classes.ClassesBase
    {
        private readonly IDnDClient _client;

        public ClassesService(IDnDClient client)
        {
            _client = client ?? throw new ArgumentNullException(nameof(client));
        }

        public override async Task<GetAllResponse> GetAll(Empty request, ServerCallContext context)
        {
            var classes = await _client.GetAllClassesAsync();
            
            var result = new GetAllResponse();
            result.Data.AddRange(classes.Results.Select(c => new GetAllResponse.Types.ClassArchiveItem()
            {
                Id = c.Index,
                Name = c.Name
            }));

            return result;
        }
}

Technically speaking, we’re done. That’s it. Finito.

However, since we’ll be calling this service from a browser, we have to deal with CORS. It’s not that complex, it just has to do in the right way.

The first step is to update the ConfigureServices() method in Startup.cs and define the policy:

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

In our small example we’re allowing everybody and their dog to call our gRPC service. In a real world scenario you’ll want to restrict to just a bunch of known clients.

The final step is, still in Startup.cs, to update the _Configure() **_method and register the necessary middlewares:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
            app.UseRouting();
            app.UseCors();
            app.UseGrpcWeb();

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

Don’t forget that the order of the middlewares matters. A lot.

Have fun!

Did you like this post? Then