gRPC With Blazor, C# And .Net Core
source link: https://dotnetgik.com/2020/01/21/grpc-with-blazor-c-and-net-core/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Agenda
- Introduction
- Project setup
- Implementing gRPC Service
- Implementing Blazor App
- Integrating the Blazor and gRPC Service
- Source Code
Introduction
gRPC has become the main talking point recently after the launch of the .Net core 3.0. This article will demonstrate one ToDo Blazor app which will be integrated to the gRPC service and perform the basic CRUD operation. If you are new to the gRPC and its terminologies just go through my previous articles.
Project setup
To get the gRPC Service as well as Blazor server up and running we need basic installation of two things
- .Net Core SDK 3.0 or Later
- Visual studio 2019
Now there are two types of projects we are going to add here first is the gRPC Project and another will be the gRPC service project. Adding gRPC Service Go to Add New Project and select gRPC Template which is Present as shown below image
Adding Blazor App
Here Open Add New Project wizard and select the Blazor App which will be the server-side Blazor App like in the image below.
Implementing gRPC Service
We have seen how we have set up the gRPC service project and then the client-side blazor app now in this section let’s implement the service and the basic CRUD operation with it. As we all know gRPC uses ProtoBuff ( Protocol Buffers ) as an Interface Definition language for defining services and structure of the payload message. In our case, we are going to implement the ToDo app which will perform all basic ToDo CRUD operations. The first step in implementing the gRPC service is to implement the protobuff file lets see step by step how we can manipulate the ProtoBuff file
Add New ProtoBuff File
To add new Proto file Right-click on the ToDoGrpc Service Project it will open add new Item wizard lets search for protocol buffer option Let’s name it ToDo.Proto
Before compiling this we need to make sure are we open the .csProj file of your project and make the following changes once so that our proto file will be compiled and it will generate the stub accordingly
<ItemGroup>
<Protobuf Include=
"Protos\greet.proto"
GrpcServices=
"Server"
/>
<Protobuf Include=
"Protos\ToDo.proto"
GrpcServices=
"Server"
/>
<Protobuf Include=
"Protos\Sample.proto"
GrpcServices=
"Server"
/>
</ItemGroup>
With these two changes, we are set for developing our services in the following steps
Defining Protocol Format
When we have added ToDo.Proto file we will have the following structure like below
syntax =
"proto3"
;
option csharp_namespace =
"ToDoGrpcService"
;
import
"google/protobuf/empty.proto"
;
package ToDo;
service ToDoService{
rpc GetToDo(google.protobuf.Empty) returns (ToDoItems);
rpc GetToDoItem(ToDoQuery) returns (ToDoData );
rpc PostToDoItem(ToDoData) returns(ToDoPostResponse);
rpc PutToDoItem(ToDoPutQuery) returns(ToDoPostResponse);
rpc DeleteItem(ToDoQuery) returns (ToDoPostResponse);
}
message ToDoData{
int32 Id=4;
string
Title=1;
string
Description=2;
bool
Status=3;
}
message ToDoQuery{
int32 id=1;
}
message ToDoItems{
repeated ToDoData ToDoItemList =1;
}
message ToDoPutQuery{
ToDoData ToDoDataItem=1;
int32 Id=2;
}
message ToDoPostResponse{
string
StatusMessage=1;
bool
Status=2;
int32 StatusCode=3;
}
Code Explanation
Package Declaration
syntax = "proto3";
option csharp_namespace = "ToDoGrpcService";
import "google/protobuf/empty.proto";
package ToDo;
Here we have to do some initial setup and import some packages. In the first line we are specifying the proto3 as a syntax for the proto file. Next is the CSharp namespace which will be the default namespace for the stub which will be generated. Next is the import section where we will be importing the empty.proto file which we are going to use in the next section.
Defining the Message Payload
message ToDoData{
int32 Id=4;
string Title=1;
string Description=2;
bool Status=3;
}
message ToDoQuery{
int32 id=1;
}
message ToDoItems{
repeated ToDoData ToDoItemList =1;
}
message ToDoPutQuery{
ToDoData ToDoDataItem=1;
int32 Id=2;
}
message ToDoPostResponse{
string StatusMessage=1;
bool Status=2;
int32 StatusCode=3;
}
Code Explanation
Message NameFieldsDescriptionToDoDataId,TitleDescription,StatusThis is the basic format for the ToDo list where Id , Title Description and status will be held.ToDoQueryidThis data structure will be used while querying the ToDo ItemToDoItemsRepeated ToDoDataThis will be a list of the ToDo Data ItemsToDoPutQueryToDoData ToDoDataItemIdThis will be used in updating the ToDo Record where Id will hold the Id of the ToDo ItemToDoPostResponseStatusMessage,Status,StatusCodeThis will be the Data which will be returned after All POST Operations.
Service Declaration
service ToDoService{
rpc GetToDo(google.protobuf.Empty) returns (ToDoItems);
rpc GetToDoItem(ToDoQuery) returns (ToDoData );
rpc PostToDoItem(ToDoData) returns(ToDoPostResponse);
rpc PutToDoItem(ToDoPutQuery) returns(ToDoPostResponse);
rpc DeleteItem(ToDoQuery) returns (ToDoPostResponse);
}
Message Payload will define the data structure which will be passed to and from the service here we have the service definitions.
Service NameParamsReturnsDescriptionGetToDoGoogle.Protobuff.EmptyToDoItemsIt gets all the To Do Items from the databaseGetToDoItemToDoQueryToDoDataIt will give the Item based on the Id which is passed in the QueryPostToDoItemToDoDataToDoPostResponseThis service will create new ToDo Item in DbPutToDoItemToDoPutQeryToDoPostResponseIt will update the Existing ToDo ItemDeleteItemToDoQueryToDoPostResponseDeletes the ToDoItem
So far we have defined the services and messages. Now when these proto files are compiled successfully it will generate some stub which will be the base for the service implementation.
Service Implementations
Below is the code which will implement the actual service
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
namespace ToDoGrpcService.Services
{
public class ToDoDataService : ToDoService.ToDoServiceBase
{
private readonly ToDoDataContext _dataContext;
public ToDoDataService(ToDoDataContext dataContext)
{
_dataContext = dataContext;
}
/// <summary>
/// Get All Data
/// </summary>
/// <param name="request"></param>
/// <param name="context"></param>
/// <returns></returns>
public override Task<ToDoItems> GetToDo(Empty request, ServerCallContext context)
{
ToDoItems objItems = new ToDoItems();
foreach (var item in _dataContext.ToDoDbItems)
{
objItems.ToDoItemList.Add(item);
}
return Task.FromResult(objItems);
}
/// <summary>
/// Post Data
/// </summary>
/// <param name="request"></param>
/// <param name="context"></param>
/// <returns></returns>
public override Task<ToDoPostResponse> PostToDoItem(ToDoData request, ServerCallContext context)
{
_dataContext.ToDoDbItems.Add(request);
var result = _dataContext.SaveChanges();
if (result>0)
{
return Task.FromResult(new ToDoPostResponse()
{
Status = true,
StatusCode = 100,
StatusMessage = "Added Successfully"
});
}
else
{
return Task.FromResult(new ToDoPostResponse()
{
Status = false,
StatusCode = 500,
StatusMessage = "Issue Occured."
});
}
}
/// <summary>
/// Get Item with the Id
/// </summary>
/// <param name="request"></param>
/// <param name="context"></param>
/// <returns></returns>
public override Task<ToDoData> GetToDoItem(ToDoQuery request, ServerCallContext context)
{
var result = from data in _dataContext.ToDoDbItems
where data.Id == request.Id
select data;
return Task.FromResult(result.First());
}
/// <summary>
/// Deletes the Item
/// </summary>
/// <param name="request"></param>
/// <param name="context"></param>
/// <returns></returns>
public override Task<ToDoPostResponse> DeleteItem(ToDoQuery request, ServerCallContext context)
{
var item = (from data in _dataContext.ToDoDbItems
where data.Id == request.Id
select data).Single();
_dataContext.ToDoDbItems.Remove(item);
var result = _dataContext.SaveChanges();
if (result > 0)
{
return Task.FromResult(new ToDoPostResponse()
{
Status = true,
StatusCode = 100,
StatusMessage = "Deleted Successfully"
});
}
else
{
return Task.FromResult(new ToDoPostResponse()
{
Status = false,
StatusCode = 500,
StatusMessage = "Issue Occured."
});
}
}
/// <summary>
/// Updates the item
/// </summary>
/// <param name="request"></param>
/// <param name="context"></param>
/// <returns></returns>
public override Task<ToDoPostResponse> PutToDoItem(ToDoPutQuery request, ServerCallContext context)
{
_dataContext.ToDoDbItems.Update(request.ToDoDataItem);
var result = _dataContext.SaveChanges();
if (result > 0)
{
return Task.FromResult(new ToDoPostResponse()
{
Status = true,
StatusCode = 100,
StatusMessage = "Updated Successfully "
});
}
else
{
return Task.FromResult(new ToDoPostResponse()
{
Status = false,
StatusCode = 500,
StatusMessage = "Issue Occured."
});
}
}
}
}
Code Explanation
Here we are adding a class ToDoDataService.cs which is getting inherited from the ToDoService.ToDoServiceBase class which is nothing but the stub generated from our proto file which will have the definition of the services which we have defined in the Proto file. Next we have implemented all the methods in the Proto file which will perform the basic operations which we perform normally.
Startup 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.EntityFrameworkCore;
using
Microsoft.Extensions.DependencyInjection;
using
Microsoft.Extensions.Hosting;
using
ToDoGrpcService.Services;
namespace
ToDoGrpcService
{
public
class
Startup
{
// 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();
services.AddDbContext<ToDoDataContext>(options =>options.UseInMemoryDatabase(
"ToDoDatabase"
));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public
void
Configure(IApplicationBuilder app, IWebHostEnvironment env,ToDoDataContext ctx)
{
if
(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
new
ToDoGenerator(ctx).ToDoDataSeed();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<GreeterService>();
endpoints.MapGrpcService<ToDoDataService>();
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"
);
});
});
}
}
}
Here a couple of changes are needed to get our service up and running. Map the endpoints to the gRPC service. In the first method, ConfigureServices, we are adding the gRPC Services in the collection and for our demo purpose we are using InMemoryDatabase so we are configuring the DbContext to use the InMemory Database The next set of Changes is needed at the Configure Method where we will do Some Database seeding which will add the initial items in the database. The next change is at the map endpoints to the gRPC service. Here we are mapping the gRPC service implementation ToDoDataService to the endpoints. Now we have implemented the Service. Let’s try to implement the Client app which will be our Blazor server-side app. In the first project section we have added one Blazor project now let’s see the basic components which will operate for us. Follow the below step to get our app working
Initial setup and Nuget packages
Once we are done with adding a project we need to add some NuGet packages which will help in implementing the clients for the gRPC Nuget Package which will be needed will be like below,
- Google.ProtoBuff
- Grpc.Net.Client
- Grpc.Tools
Once we are done with the packages let’s configure our client to compile and include the proto files
Adding Proto File and Configure gRPC Client
gRPC Clients are nothing but concrete types which are generated from the Proto files to generate the gRPC Client. Let’s add our proto files in the Project; for that let us make a folder Called Proto. Once they are added in the Project let’s change and configure the Proto files to be compiled as a Client. In the service section we have made them be compiled as a service so let’s modify our .csproj file and make them a client like below.
<ItemGroup>
<Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
<Protobuf Include="Protos\ToDo.proto" GrpcServices="Client" />
</ItemGroup>
In this Section we have modified and made sure we are compiling our proto files as a client. How we can implement the client? Let’s see while developing the services.
Add New Razor Component
Right-click on project -> Add New Item ->> Add New Razor Component. Name it ToDoOperation.razor like the image below.
Code for the Razor Component will be like below
@page
"/Todo"
@inherits BlazorClient.CodeFiles.ToDoOperation
@
if
(toDoItems ==
null
&& toDoItems.ToDoItemList ==
null
)
{
<p><em>Loading...</em></p>
}
<div style=
"background-color:#3a0647;color:white;font-family:Calibri;font-size:x-large;font-weight:500;text-align:center;border-radius:10px"
>
To Do List
</div>
<hr style=
"color:#5c116f"
/>
<div
class
=
"row"
style=
"padding-left:900px;padding-bottom:4px"
>
<button id=
"btnAdd"
@onclick=
"ShowAddpopup"
style=
"background-color:#5c116f;color:white;font-family:Calibri"
>Add New ToDo</button>
</div>
@
if
(toDoItems !=
null
&& toDoItems.ToDoItemList !=
null
)
{
<div
class
=
"row"
style=" text-align:center; background-color:#5c116f;color:white;font-family:Calibri;font-size:larger;
border-radius:7px; font-weight:500">
<div
class
=
"col-sm-2"
>Sr. No.</div>
<div
class
=
"col-sm-2"
>Title</div>
<div
class
=
"col-sm-2"
>Description</div>
<div
class
=
"col-sm-2"
>Status</div>
</div>
@
for
(
int
i = 0; i < toDoItems?.ToDoItemList?.Count; i++)
{
var
a = @toDoItems.ToDoItemList[i].Id;
<div
class
=
"row"
style=
"text-align:center;font-family:Calibri;font-size:medium;font-weight:500;padding:1px"
>
<div
class
=
"col-sm-2"
>@(i+1)</div>
<div
class
=
"col-sm-2"
>@toDoItems.ToDoItemList[i].Title</div>
<div
class
=
"col-sm-2"
>@toDoItems.ToDoItemList[i].Description</div>
<div
class
=
"col-sm-2"
>@toDoItems.ToDoItemList[i].Status</div>
<div
class
=
"col-sm-2"
style=
"text-align:left"
>
<button
class
=
"btn btn-primary"
@onclick=
"@(async () => await ShowEditForm(a))"
>Edit</button>
</div>
<div
class
=
"col-sm-2"
style=
"text-align:left"
> <button
class
=
"btn btn-danger"
@onclick=
"@(async () => ShowDeletePopup(a.ToString()))"
>Remove</button> </div>
</div>
}
}
else
{
<div
class
=
"row"
style=
"text-align:center;font-family:Calibri;font-size:medium;font-weight:500;padding:1px"
>
<h4> No To Do Item Found !! </h4>
</div>
}
@
if
(ShowModel ==
true
)
{
<div
class
=
"modal"
tabindex=
"-1"
style=
"display:block;"
role=
"dialog"
>
<div
class
=
"modal-dialog"
>
<div
class
=
"modal-content"
>
<div
class
=
"modal-header"
style=
"background-color:#5c116f;color:white;height:50px"
>
<span
class
=
"modal-title"
>@PopupTitle</span>
<button type=
"button"
class
=
"close"
@onclick=
"DismissPopup"
>
<span aria-hidden=
"true"
style=
"color:white;"
>X</span>
</button>
</div>
<div
class
=
"modal-body"
>
<table border=
"0"
cellspacing=
"1"
>
<tr>
<td><strong>Title</strong></td>
<td><input type=
"text"
@bind=
"ToDoDataItem.Title"
maxlength=
"20"
/></td>
</tr>
<tr>
<td><strong>Description</strong></td>
<td><input type=
"text"
@bind=
"ToDoDataItem.Description"
maxlength=
"20"
/></td>
</tr>
<tr>
<td><strong>Status</strong></td>
<td><input type=
"checkbox"
@bind=
"ToDoDataItem.Status"
/></td>
</tr>
<tr>
<td colspan=
"2"
align=
"center"
><button
class
=
"btn btn-primary"
id=
"btnPostData"
@onclick=
"PostData"
>@ActionText</button></td>
</tr>
</table>
</div>
</div>
</div>
</div>
}
@
if
(ShowAlert ==
true
)
{
<div
class
=
"modal"
tabindex=
"-2"
style=
"display:block;padding-top:-200px;padding-right:0px"
role=
"dialog"
>
<div
class
=
"modal-dialog"
>
<div
class
=
"modal-content"
>
<div
class
=
"modal-header"
style=
"background-color:#5c116f;color:white;height:50px"
>
<span
class
=
"modal-title"
>Notification</span>
<button type=
"button"
class
=
"close"
@onclick=
"DismissPopup"
>
<span aria-hidden=
"true"
style=
"color:white;"
>X</span>
</button>
</div>
<div
class
=
"modal-body"
>
@OperationStatusText
</div>
</div>
</div>
</div>
}
@
if
(ShowModeletePopup ==
true
)
{
<div
class
=
"modal"
tabindex=
"-3"
style=
"display:block;padding-top:300px"
role=
"dialog"
>
<div
class
=
"modal-dialog"
>
<div
class
=
"modal-content"
>
<div
class
=
"modal-header"
style=
"background-color:#5c116f;color:white;height:50px"
>
<span
class
=
"modal-title"
>Status</span>
<button type=
"button"
class
=
"close"
@onclick=
"DismissPopup"
>
<span aria-hidden=
"true"
style=
"color:white;"
>X</span>
</button>
</div>
<div
class
=
"modal-body"
>
<table>
<tr>
<td colspan=
"2"
>
Are you sure you want to delete
this
ToDo Item with Id @DeleteItemId ?
</td>
</tr>
<tr>
<td align=
"right"
><button
class
=
"btn btn-primary"
@onclick=
"DeleteData"
>Ok</button></td>
<td align=
"left"
><button
class
=
"btn btn-danger"
>Cancel</button></td>
</tr>
</table>
</div>
</div>
</div>
</div>
}
Add Component Class
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Threading.Tasks;
using
Grpc.Net.Client;
using
Microsoft.AspNetCore.Components;
namespace
BlazorClient.CodeFiles
{
public
partial
class
ToDoOperation : ComponentBase
{
public
bool
ShowModel =
false
;
public
bool
ShowAlert =
false
;
public
bool
ShowModeletePopup =
false
;
public
string
OperationStatusText =
""
;
public
string
PopupTitle =
""
;
public
BlazorClient.Data.ToDoDataItem ToDoDataItem =
null
;
public
string
ActionText =
""
;
public
ToDoGrpcService.ToDoItems toDoItems;
public
string
DeleteItemId {
get
;
set
; }
[Inject]
protected
BlazorClient.Services.ToDoDataService ToDoService {
get
;
set
; }
protected
override
void
OnInitialized()
{
GetToDoList();
}
protected
void
GetToDoList()
{
toDoItems= ToDoService.GetToDoList();
}
protected
async
Task ShowEditForm(
int
Id)
{
PopupTitle =
"To Do Edit"
;
ActionText =
"Update"
;
ToDoDataItem = ToDoService.GetToDoItem(Id);
ShowModel =
true
;
}
protected
void
ShowAddpopup()
{
ToDoDataItem =
new
Data.ToDoDataItem() { Title =
""
, Description =
""
, Status =
false
, Id = 0 };
PopupTitle =
"To Do Add"
;
ActionText =
"Add"
;
ShowModel =
true
;
}
protected
void
ShowDeletePopup(
string
Id)
{
DeleteItemId = Id;
ShowModeletePopup =
true
;
}
protected
void
PostData()
{
bool
status =
false
;
if
(ToDoDataItem.Id > 0)
{
status = ToDoService.UpdateToDoData(
this
.ToDoDataItem);
}
else
{
status = ToDoService.AddToDoData(
this
.ToDoDataItem);
}
Reload(status);
}
public
void
DeleteData()
{
var
operationStatus = ToDoService.DeleteData(DeleteItemId);
Reload(operationStatus);
}
protected
void
Reload(
bool
status)
{
ShowModeletePopup =
false
;
ShowModel =
false
;
GetToDoList();
ShowAlert =
true
;
if
(status)
{
OperationStatusText =
"Processed Successfully !! "
;
}
else
{
OperationStatusText =
"Error Occured "
;
}
}
protected
void
DismissPopup()
{
ShowModel =
false
;
ShowAlert =
false
;
ShowModeletePopup =
false
;
}
}
}
Here in our code-behind code, we have injected a service ToDoDataService which will hold all our gRPC operations.
Design Service to Call gRPC Methods
Code for the ToDoData Service will be like below.
using
Grpc.Net.Client;
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Threading.Tasks;
namespace
BlazorClient.Services
{
public
class
ToDoDataService
{
private
ToDoGrpcService.ToDoService.ToDoServiceClient GetServiceClient()
{
return
new
ToDoGrpcService.ToDoService.ToDoServiceClient(channel);
}
public
bool
AddToDoData(Data.ToDoDataItem toDoDataItem)
{
var
client = GetServiceClient();
var
todoData =
new
ToDoGrpcService.ToDoData()
{
Status = toDoDataItem.Status,
Title = toDoDataItem.Title,
Description = toDoDataItem.Description
};
var
response = client.PostToDoItem(todoData,
null
);
return
response.Status;
}
public
bool
UpdateToDoData(Data.ToDoDataItem toDoDataItem)
{
var
client = GetServiceClient();
var
updateData =
new
ToDoGrpcService.ToDoPutQuery();
updateData.Id = toDoDataItem.Id;
updateData.ToDoDataItem =
new
ToDoGrpcService.ToDoData()
{
Id = toDoDataItem.Id,
Status = toDoDataItem.Status,
Title = toDoDataItem.Title,
Description = toDoDataItem.Description
};
var
response = client.PutToDoItem(updateData,
null
);
return
response.Status;
}
public
bool
DeleteData(
string
ToDoId)
{
var
client = GetServiceClient();
var
response = client.DeleteItem(
new
ToDoGrpcService.ToDoQuery() { Id = Convert.ToInt32(ToDoId) },
null
);
return
response.Status;
}
public
ToDoGrpcService.ToDoItems GetToDoList()
{
var
client = GetServiceClient();
return
client.GetToDo(
new
Google.Protobuf.WellKnownTypes.Empty(),
null
);
}
public
Data.ToDoDataItem GetToDoItem(
int
id)
{
var
client = GetServiceClient();
var
todoItem = client.GetToDoItem(
new
ToDoGrpcService.ToDoQuery() { Id = Convert.ToInt32(id) },
null
);
return
new
Data.ToDoDataItem() { Title = todoItem.Title, Description = todoItem.Description, Status = todoItem.Status, Id = todoItem.Id };
}
}
}
Here as you can see we have added all our methods like Add, Update, Delete Get Methods which will call their operations accordingly. Here we are creating the gRPC Client and that will be used to call all the operations.
Add Service in the Service Collection
To make sure service is available in the application let’s add the service in the collection with a single line like below.
services.AddSingleton<BlazorClient.Services.ToDoData
Once we have all this implemented we will have an output like below,
You can find all this source code here Source code
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK