Introduction
In my last article, we saw how do we integrate the Blazor server-side with the gRPC service. In this article, let’s try to explore the integration between web assembly and gRPC service. Also, we will try to build a To-Do Application where the front end is into the Blazor Web assembly and back end designed into the gRPC. If you want to know and explore gRPC, refer to my previous articles.
Project Setup
To get started with this, let's set up the project first before going forward. We need to ensure we have the following things in place:
- .NET Core SDK 3.0 and above (try to get latest stable version)
- Visual Studio 2019 – Latest edition
We have two projects, the first one is the web Assembly project and another is the gRPC service project. Let's add them one by one.
Blazor web Assembly
Open Visual Studio and click on Add New Project and Select the following project type:
Once we click on the above step, go to the next step, then we get the following options:
After doing the needed steps, we will arrive at this step which will be like below:
Select Blazor Web Assembly App here and our project will be created with this just to clarify one thing we are creating standalone application here without any server or back end support as we will have our own back end service for integration.
Creating a gRPC Service Project
Now let us add the gRPC project. To add that, again go to visual studio and create New Project and select the following option
Implementing gRPC service
We are going to use the same service which we have used in the last article which is
here. In this article, we will focus on only the implementation details which are new and needed for understanding the integration between the Blazor web assembly and the gRPC service.
Startup.cs Changes
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading.Tasks;
- using Microsoft.AspNetCore.Builder;
- using Microsoft.AspNetCore.Hosting;
- using Microsoft.AspNetCore.Http;
- using Microsoft.Extensions.DependencyInjection;
- using Microsoft.Extensions.Hosting;
- using ToDoGrpcService;
- using ToDoGrpcService.Services;
- using Microsoft.EntityFrameworkCore;
- namespace ToDo
- {
- public class Startup
- {
-
-
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddCors(o => o.AddPolicy("AllowAll", builder =>
- {
- builder.AllowAnyOrigin()
- .AllowAnyMethod()
- .AllowAnyHeader();
-
- }));
- services.AddGrpc();
- services.AddDbContext<ToDoDataContext>(options => options.UseInMemoryDatabase("ToDoDatabase"));
- }
-
-
- public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ToDoDataContext ctx)
- {
- new ToDoGenerator(ctx).ToDoDataSeed();
-
- if (env.IsDevelopment())
- {
- app.UseDeveloperExceptionPage();
- }
-
- app.UseRouting();
- app.UseCors();
- app.UseGrpcWeb();
- app.UseEndpoints(endpoints =>
- {
- endpoints.MapGrpcService<GreeterService>().EnableGrpcWeb()
- .RequireCors("AllowAll");
- endpoints.MapGrpcService<ToDoDataService>().EnableGrpcWeb()
- .RequireCors("AllowAll");
-
- 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");
- });
- });
- }
- }
- }
The most notable change that we can see here is adding the CORS policy and applying to the services next notable change is to add the middleware GrpcWeb in the pipeline and enabling the endpoints for calling them from browser.
What is gRPC-Web?
As we all know, it is not possible to call grpc service directly from the browser and it needs to be consumed from the grpc enabled client. gRPC – Web is a protocol that allows browser and JavaScript and blazor wasm clients to call the service easily.
Configure gRPC- Web
To see that let's check the following below steps.
Add nuget package Grpc.AspNetCore.Web
Configure App by Adding UseGrpcWeb and EnableGrpcWeb in Startup.cs like below:
- app.UseGrpcWeb();
- app.UseEndpoints(endpoints =>
- {
- endpoints.MapGrpcService<GreeterService>().EnableGrpcWeb()
- .RequireCors("AllowAll");
- endpoints.MapGrpcService<ToDoDataService>().EnableGrpcWeb()
- .RequireCors("AllowAll");
- }
Code Details
- The above code adds grp-Web Middleware using UseGrpcWeb() after routing and before endpoints
- It specifies that Services are enabled for gRPC web
gRPC-Web and CORS
We all know that the browser generally prevents calling the services from a domain other than the domain where your app is hosted. the same restriction is applied to the gRPC-Web also in order to apply for the browser to make CORS calls with the following snippet.
- services.AddCors(o => o.AddPolicy("AllowAll", builder =>
- {
- builder.AllowAnyOrigin()
- .AllowAnyMethod()
- .AllowAnyHeader()
- .WithExposedHeaders("Grpc-Status", "Grpc-Message", "Grpc-Encoding", "Grpc-Accept-Encoding");
- }));
Implement Blazor Application
Now we are ready with the server application its time we Blazor application which will use these services.
Install Packages
In order to get the grpc services up and running, let's install the following packages in the application:
- Google.Protobuf
- Grpc.Net.Client
- Grpc.Net.Client.Web
- Grpc.Tools
Once we are done with these package installations, lets copy our proto files from the server application and paste in the folder named proto in the blazor application
Once we are done with the copy and paste, then change the properties of the file to compile these files as a client like below:
Once we are done with this build, our project once and we will have our stubs automatically generated to use in our application
Configuring Client DI service in Program.cs
The most significant change that we need to do here in the application is to inject the services into the client application for this we have to make changes in the Program.cs, like below:
- using System;
- using System.Net.Http;
- using System.Threading.Tasks;
- using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
- using Microsoft.Extensions.DependencyInjection;
- using Grpc.Net.Client.Web;
- using Microsoft.AspNetCore.Components;
- using Grpc.Net.Client;
- using ToDo;
-
- namespace BlazorWebAseemblyWithGrpc
- {
- public class Program
- {
- public static async Task Main(string[] args)
- {
- var builder = WebAssemblyHostBuilder.CreateDefault(args);
- builder.RootComponents.Add<App>("app");
-
- builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
-
- builder.Services.AddSingleton<BlazorClient.Services.ToDoDataService>();
-
- builder.Services.AddSingleton(services =>
- {
- var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
- var baseUri = services.GetRequiredService<NavigationManager>().BaseUri;
- var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions { HttpClient = httpClient });
-
-
- return new Greeter.GreeterClient(channel);
-
- });
- builder.Services.AddSingleton(services =>
- {
- var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
- var baseUri = services.GetRequiredService<NavigationManager>().BaseUri;
- var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions { HttpClient = httpClient });
-
-
- return new ToDoGrpcService.ToDoService.ToDoServiceClient(channel);
-
- });
- await builder.Build().RunAsync();
- }
- }
- }
If you see here, we are injecting our ToDo service as a singleton which can be used in the application by injecting the service into the data service next step will be to add the Data service. Add the Code file and the component.
Designing the data service
- using Grpc.Net.Client;
- using System;
- using System.Threading.Tasks;
- using ToDoGrpcService;
- namespace BlazorClient.Services
- {
- public class ToDoDataService
- {
- ToDoService.ToDoServiceClient _toDoServiceClient;
- public ToDoDataService(ToDoService.ToDoServiceClient toDoServiceClient)
- {
- _toDoServiceClient = toDoServiceClient;
- }
-
- public async Task<bool> AddToDoData(Data.ToDoDataItem toDoDataItem)
- {
- var todoData = new ToDoGrpcService.ToDoData()
- {
- Status = toDoDataItem.Status,
- Title = toDoDataItem.Title,
- Description = toDoDataItem.Description
- };
-
- var response = await _toDoServiceClient.PostToDoItemAsync(todoData, null);
- return response.Status;
-
- }
- public async Task<bool> UpdateToDoData(Data.ToDoDataItem toDoDataItem)
- {
-
- var updateData = new ToDoGrpcService.ToDoPutQuery
- {
- Id = toDoDataItem.Id,
- ToDoDataItem = new ToDoGrpcService.ToDoData()
- {
- Id = toDoDataItem.Id,
- Status = toDoDataItem.Status,
- Title = toDoDataItem.Title,
- Description = toDoDataItem.Description
- }
- };
- var response = await _toDoServiceClient.PutToDoItemAsync(updateData, null);
- return response.Status;
- }
- public async Task<bool> DeleteDataAsync(string ToDoId)
- {
-
- var response = await _toDoServiceClient.DeleteItemAsync(new ToDoGrpcService.ToDoQuery() { Id = Convert.ToInt32(ToDoId) }, null);
- return response.Status;
- }
- public async Task<ToDoGrpcService.ToDoItems> GetToDoList()
- {
-
- return await _toDoServiceClient.GetToDoAsync(new Google.Protobuf.WellKnownTypes.Empty(), null);
-
- }
-
- public async Task<Data.ToDoDataItem> GetToDoItemAsync(int id)
- {
-
- var todoItem = await _toDoServiceClient.GetToDoItemAsync(new ToDoGrpcService.ToDoQuery() { Id = Convert.ToInt32(id) }, null);
-
-
-
- return new Data.ToDoDataItem() { Title = todoItem.Title, Description = todoItem.Description, Status = todoItem.Status, Id = todoItem.Id };
-
- }
- }
- }
Add Code file for Component
Component Template
Once we are done with the above changes, we can see the output below:
Here is how we can integrate our Blazor web assembly application to the grpc services. For the complete source code, you can follow my
Git Hub Repo.
References
- https://blog.stevensanderson.com/2020/01/15/2020-01-15-grpc-web-in-blazor-webassembly/
- https://docs.microsoft.com/en-us/aspnet/core/grpc/browser?view=aspnetcore-3.1