

ToDo App with Blazor wasm and Grpc
source link: https://dotnetgik.com/2020/06/28/todo-app-with-blazor-wasm-and-grpc/
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.

ToDo App with Blazor wasm and Grpc
Agenda
- Introduction
- Project Setup
- Implementing gRPC service
- Implement Blazor Application
- Integrate Service with Client
- Conclusion
Introduction
In My last article we have seen how do we integrate the Blazor server side with the gRPC service in this article let’s try to explore integration between web assembly and gRPC service also we will try to build a To Do Application where front end is into the Blazor Web assembly and back end designed into the gRPC . if you want to know and explore gRPC refer my previous articles
Project Setup
To get started with this lets setup the project first before going forward we need to ensure we have 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 first one is the web Assembly project and another is the gRPC service project lets add them one by one
- Blazor web Assembly
Open Visual Studio and click on Add new Project and Select following project type

Once we click on above step go to next step then we get the following options

After doing 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 the integration
- Creating 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

One we complete all the step we will have our service ready and we are done with the project setups. Once we are done with the project setups its time we implement the gRPC service
Implementing gRPC service
We are going to use same service which we have used in the last article which is here so 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
{
// 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.AddCors(o => o.AddPolicy(
"AllowAll"
, builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
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)
{
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"
);
});
});
}
}
}
Most notable change that we can see here is the 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 which allows browser and JavaScript and blazor wasm clients to call the service easily .
Configure gRPC- Web
To see that lets check the follow below steps
- Add nuget package Grpc.AspNetCore.Web
- Configure App by Adding UseGrpcWeb and EnableGrpcWeb in Startup.cs like below
Code Details
app.UseGrpcWeb();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<GreeterService>().EnableGrpcWeb()
.RequireCors(
"AllowAll"
);
endpoints.MapGrpcService<ToDoDataService>().EnableGrpcWeb()
.RequireCors(
"AllowAll"
);
}
- Above code adds grp-Web Middleware using UseGrpcWeb() after routing and before endpoints
- Specifies that Services are enabled for gRPC web
gRPC-Web and CORS
we all know browser generally prevents calling the services from domain other than the domain where your app is hosted . same restriction is applied to the gRPC-Web also in order to apply for the browser to make CORS calls with 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 lets install following packages in the application
- Google.Protobuf
- Grpc.Net.Client
- Grpc.Net.Client.Web
- Grpc.Tools
Once we are done with these package installation lets copy our proto files from server application and paste in the folder named proto in the blazor application
Once we are done with 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 });
// Now we can instantiate gRPC clients for this channel
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 });
// Now we can instantiate gRPC clients for this channel
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 adding the component
Designing the data 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."
});
}
}
}
}
Add Code file for Component
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
int
noOfRows = 0;
public
int
noOfCoumns = 4;
public
BlazorClient.Data.ToDoDataItem ToDoDataItem =
new
BlazorClient.Data.ToDoDataItem();
public
string
ActionText =
""
;
public
ToDoGrpcService.ToDoItems toDoItems =
new
ToDoGrpcService.ToDoItems();
public
string
DeleteItemId {
get
;
set
; }
[Inject]
protected
BlazorClient.Services.ToDoDataService ToDoService {
get
;
set
; }
protected
async
override
Task OnInitializedAsync()
{
try
{
await
GetToDoListAsync();
}
catch
(Exception ex)
{
Console.WriteLine(ex);
}
}
protected
async
Task GetToDoListAsync()
{
toDoItems =
await
ToDoService.GetToDoList();
var
temoRowCount = toDoItems.ToDoItemList.Count() / noOfCoumns;
noOfRows = toDoItems.ToDoItemList.Count() % noOfCoumns == 0 ? temoRowCount : temoRowCount + 1;
}
protected
async
Task ShowEditForm(
int
Id)
{
Console.Write(Id);
PopupTitle =
"To Do Edit"
;
ActionText =
"Update"
;
ToDoDataItem =
await
ToDoService.GetToDoItemAsync(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)
{
Console.Write(Id);
DeleteItemId = Id;
ShowModeletePopup =
true
;
}
protected
async
Task PostDataAsync()
{
try
{
bool
status =
false
;
if
(ToDoDataItem.Id > 0)
{
status =
await
ToDoService.UpdateToDoData(
this
.ToDoDataItem);
}
else
{
status =
await
ToDoService.AddToDoData(
this
.ToDoDataItem);
}
await
Reload(status);
}
catch
(Exception ex)
{
Console.WriteLine(ex);
}
}
public
async
Task DeleteDataAsync()
{
var
operationStatus =
await
ToDoService.DeleteDataAsync(DeleteItemId);
await
Reload(operationStatus);
}
protected
async
Task Reload(
bool
status)
{
ShowModeletePopup =
false
;
ShowModel =
false
;
await
GetToDoListAsync();
ShowAlert =
true
;
if
(status)
{
OperationStatusText =
"1"
;
}
else
{
OperationStatusText =
"0"
;
}
}
protected
void
DismissPopup()
{
ShowModel =
false
;
ShowAlert =
false
;
ShowModeletePopup =
false
;
}
}
}
Component Template
@page
"/Todo"
@inherits BlazorClient.CodeFiles.ToDoOperation
<div
class
=
"row"
style=
"margin-bottom:15px"
>
<button id=
"btnAdd"
@onclick=
"ShowAddpopup"
class
=
"btn btn-primary"
style=
"background-color: #053870; color: white"
><i
class
=
"oi oi-plus"
>Add New</i></button>
</div>
@
if
(toDoItems !=
null
&& toDoItems.ToDoItemList !=
null
)
{
int
elementStartPosition = 0;
@
for
(
int
i = 0; i < noOfRows; i++)
{
<div
class
=
"row"
>
@
for
(
int
elementPosition = elementStartPosition; elementPosition < elementStartPosition + noOfCoumns; elementPosition++)
{
if
(elementPosition > toDoItems.ToDoItemList.Count() - 1)
{
break
;
}
var
idToEdit = @toDoItems.ToDoItemList[elementPosition].Id;
<div
class
=
"card"
style=
"width: 18rem;margin:3px;border-radius:10px"
>
<div
class
=
"card-body"
>
<h5
class
=
"card-title"
style=
"background-color: #053870; color: white;text-align:center;padding:5px;font-weight:500"
>@toDoItems.ToDoItemList[elementPosition].Title</h5>
<p
class
=
"card-text"
>@toDoItems.ToDoItemList[elementPosition].Description</p>
<p
class
=
"card-text"
>@(toDoItems.ToDoItemList[elementPosition].Status ==
false
?
"Closed"
:
"Active"
)</p>
</div>
<div
class
=
"card-footer text-md-center"
>
<button
class
=
"btn btn-secondary"
@onclick=
"@(async () => await ShowEditForm(idToEdit))"
>Edit</button>
<button
class
=
"btn btn-danger"
@onclick=
"@(async () => ShowDeletePopup(idToEdit.ToString()))"
>Trash</button>
</div>
</div>
}
<div style=
"display:none"
> @(elementStartPosition = elementStartPosition + noOfCoumns);</div>
</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=
"PostDataAsync"
>@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"
>
@
if
(OperationStatusText ==
"1"
)
{
<span> All Good <img draggable=
"false"
role=
"img"
class
=
"emoji"
alt=
"
"
src=
"https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/svg/1f604.svg"
></span>
}
else
{
<span>Nothing
is
good i am angry now <img draggable=
"false"
role=
"img"
class
=
"emoji"
alt=
"
"
src=
"https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/svg/1f620.svg"
></span>
}
</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=
"DeleteDataAsync"
>Ok</button></td>
<td align=
"left"
><button
class
=
"btn btn-danger"
>Cancel</button></td>
</tr>
</table>
</div>
</div>
</div>
</div>
}
Once we are done with the above changes, we can see the output like below

Here is so far how we can integrate our Blazor web assembly application to the grpc services for complete source code you can follow it on my Git Hub Repo
Source code link :: https://github.com/dotnetgik/BlazorWebAseemblyWithGrpc
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
Recommend
-
13
How to have realtime update in your app with Blazor WASM, SignalR and MediatR One of the big problem with web app is real time update : most of the traffic is from the client to the server, so the server cannot say anything to the c...
-
5
.NET 5 正式发布已经有一段时间了,其中 Blazor 技术是该版本的亮点之一。作为微软技术的被坑者,年少的我曾经以为 SilverLight 能血虐 Flash,Zune 能团灭 iPod,WP 能吊打 iPhone,UWP 能统一全平台…… 可是后…… 最终步入大龄程序员的我发现,只有陪伴了我将近...
-
12
Blazor WASM 404 error and fix for GitHub Pages Written by Kristoffer Strube, April 06, 2021 The project structure of a Blazor WASM project is in some...
-
21
Rendering dynamic content in Blazor Wasm using DynamicComponent Written by Kristoffer Strube, April 20, 2021 Sometimes we need to load different types...
-
7
Ahead-Of-Time Compilation for Blazor Wasm Written by Kristoffer Strube, September 28, 2021 Blazor Wasm applications are being interpreted in the brows...
-
4
Back to all posts MVC vs Blazor WASM: A Different Look At Performance Posted on Mar 29, 2022 When it comes to measuring the performa...
-
8
.NET WASM is still loading. You can interact in this page after it's fully loaded.Current page is prerendered.Get started Please check t...
-
3
-
7
August 30, 2022 Troubleshooting .NET Blazor WASM Debugging ...
-
4
Transforming a Blazor WebAssembly (WASM) App into a Progressive Web AppProgressive web applications (PWAs) are the future of web development, offering an app-like experience that is fast, reliable, and engaging. With...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK