Introduction
SignalR is a library for ASP.NET developers to simplify the process of adding real-time web functionality to applications. Real-time web functionality is the ability to have server code push content to connected clients instantly as it becomes available, rather than having the server wait for a client to request new data. The chat application is often used as a SignalR example, but here we will see a small book application, where we can have all the CRUD operations.
Blazor is a framework built by Microsoft for creating interactive client-side web UI with a .NET codebase. We can write both client-side and server-side code in C#.NET itself. There are two hosting models available for Blazor. Blazor Server and Blazor WebAssembly. Blazor Server for production was already available. Recently Microsoft released the production version of Blazor WebAssembly also.
Create Blazor WebAssembly App
We can create a web application with Visual Studio 2019 using the Blazor WebAssembly template. Please select the “ASP.NET Core hosted” option also.

If you look at the solution structure, we can see 3 different projects are created by default.

As we are creating a Book app, add a “Book” class in the “Shared” project.
Book. cs
namespace BlazorSignalR.Shared
{
public class Book
{
public string Id { get; set; }
public string Isbn { get; set; }
public string Name { get; set; }
public string Author { get; set; }
public double Price { get; set; }
}
}
We have five properties in the Book class.
We can install the “Microsoft.AspNetCore.SignalR.Client” library using NuGet package manager in “Client” project. This project library is already added as a dependency in “Server” project. Hence, we can use SignalR library in the Server project as well.
We must register the SignalR component inside the “ConfigureServices” method of Startup class (Server project).

public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddRazorPages();
services.AddSignalR();
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
}
Create a “BroadcastHub” class inside a new “Hubs” folder in Server project and inherit “Hub“ class from SignalR library.
BroadcastHub.cs
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace BlazorSignalR.Server.Hubs
{
public class BroadcastHub : Hub
{
public async Task SendMessage()
{
await Clients.All.SendAsync("ReceiveMessage");
}
}
}
We have added a “SendMessage” method in the above class. You can give any name for this method. It will be used to send and receive push notification using SignalR hub.
We can add the endpoints for the BroadcastHub class in the Configure method of Startup class. We named it as “broadcastHub”. This will be used in our Razor components later in Client project.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapHub<BroadcastHub>("/broadcastHub");
endpoints.MapFallbackToFile("index.html");
});
}
We can create a “BooksController” class for CRUD operations under Controllers folder using Scaffolding template.
We have used scaffolding with entity framework template. So that, all the methods for CRUD operations has been created automatically. But we have slightly changed the “PostBook” method for our purpose. Please use the below code.
BooksController.cs
using BlazorSignalR.Server.Data;
using BlazorSignalR.Shared;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BlazorSignalR.Server.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{
private readonly BooksDbContext _context;
public BooksController(BooksDbContext context)
{
_context = context;
}
// GET: api/Books
[HttpGet]
public async Task<ActionResult<IEnumerable<Book>>> GetBook()
{
return await _context.Book.ToListAsync();
}
// GET: api/Books/5
[HttpGet("{id}")]
public async Task<ActionResult<Book>> GetBook(string id)
{
var book = await _context.Book.FindAsync(id);
if (book == null)
{
return NotFound();
}
return book;
}
// PUT: api/Books/5
// To protect from overposting attacks, enable the specific properties you want to bind to, for
// more details, see https://go.microsoft.com/fwlink/?linkid=2123754.
[HttpPut("{id}")]
public async Task<IActionResult> PutBook(string id, Book book)
{
if (id != book.Id)
{
return BadRequest();
}
_context.Entry(book).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!BookExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/Books
// To protect from overposting attacks, enable the specific properties you want to bind to, for
// more details, see https://go.microsoft.com/fwlink/?linkid=2123754.
[HttpPost]
public async Task<ActionResult> PostBook(Book book)
{
book.Id = Guid.NewGuid().ToString();
_context.Book.Add(book);
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
if (BookExists(book.Id))
{
return Conflict();
}
else
{
throw;
}
}
return Ok();
}
// DELETE: api/Books/5
[HttpDelete("{id}")]
public async Task<ActionResult<Book>> DeleteBook(string id)
{
var book = await _context.Book.FindAsync(id);
if (book == null)
{
return NotFound();
}
_context.Book.Remove(book);
await _context.SaveChangesAsync();
return book;
}
private bool BookExists(string id)
{
return _context.Book.Any(e => e.Id == id);
}
}
}
We can notice that a new connection string is created in the appsettings.json file and registered the database connection context in “ConfigureServices” method of Startup class.
Open Package Manager Console from “Tools” -> “NuGet Package Manager” and use below NuGet command to create a migration script. We are using Entity framework code first approach in this application.
Add-migration Init
Above command will create a new class with current timestamp (Suffix as “_Init”), and it will be used for our data migration.
Use the NuGet command to update the database
Update-database
If you look at the SQL Server Object Explorer, you can see that a new database is created with a “Book” table.

Create Razor Components in Client project
We can create the razor components for CRUD operations in Client project.
Add “ListBooks” component inside the “Pages” folder to display all book details from database using API get method.
ListBooks.razor
@page "/listbooks"
@using BlazorSignalR.Shared
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager NavigationManager
@inject HttpClient Http
<h2>Book Details</h2>
<p>
<a href="/addbook">Create New Book</a>
</p>
@if (books == null)
{
<p>Loading...</p>
}
else
{
<table class='table'>
<thead>
<tr>
<th>Name</th>
<th>ISBN</th>
<th>Author</th>
<th>Price</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var book in books)
{
<tr>
<td>@book.Name</td>
<td>@book.Isbn</td>
<td>@book.Author</td>
<td>@book.Price</td>
<td>
<a href='/editbook/@book.Id'>Edit</a>
<a href='/deletebook/@book.Id'>Delete</a>
</td>
</tr>
}
</tbody>
</table>
}
@code {
Book[] books;
private HubConnection hubConnection;
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/broadcastHub"))
.Build();
hubConnection.On("ReceiveMessage", () =>
{
CallLoadData();
StateHasChanged();
});
await hubConnection.StartAsync();
await LoadData();
}
private void CallLoadData()
{
Task.Run(async () =>
{
await LoadData();
});
}
protected async Task LoadData()
{
books = await Http.GetFromJsonAsync<Book[]>("api/books");
StateHasChanged();
}
public bool IsConnected =>
hubConnection.State == HubConnectionState.Connected;
public void Dispose()
{
_ = hubConnection.DisposeAsync();
}
}
If you look at the code, you can see that we have initialized a SignalR hub connection inside the “OnInitializedAsync” method and also navigated to the “broadcastHub” endpoint, that we have already registered in the Startup class.

Hub connection is listening for a new push message from Hub server and it will call the “CallLoadData” method. This method will again call the LoadData method and will get new or modified Book data from database using API get method. Whenever, we add or change a book record in another web client, it will be automatically reflected in this component. Hence, we will get real-time data.
Create a new razor component “AddBook” and add below code inside the component file.
AddBook.razor
@page "/addbook"
@using Microsoft.AspNetCore.SignalR.Client
@using BlazorSignalR.Shared
@inject HttpClient Http
@inject NavigationManager NavigationManager
<h2>Create Book</h2>
<hr />
<form>
<div class="row">
<div class="col-md-8">
<div class="form-group">
<label for="Name" class="control-label">Name</label>
<input id="Name" class="form-control" @bind="@book.Name" />
</div>
<div class="form-group">
<label for="Department" class="control-label">ISBN</label>
<input id="Department" class="form-control" @bind="@book.Isbn" />
</div>
<div class="form-group">
<label for="Designation" class="control-label">Author</label>
<input id="Designation" class="form-control" @bind="@book.Author" />
</div>
<div class="form-group">
<label for="Company" class="control-label">Price</label>
<input id="Company" class="form-control" @bind="@book.Price" />
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<input type="button" class="btn btn-primary" @onclick="@CreateBook" value="Save" />
<input type="button" class="btn" @onclick="@Cancel" value="Cancel" />
</div>
</div>
</div>
</form>
@code {
private HubConnection hubConnection;
Book book = new Book();
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/broadcastHub"))
.Build();
await hubConnection.StartAsync();
}
protected async Task CreateBook()
{
await Http.PostAsJsonAsync("api/books", book);
if (IsConnected) await SendMessage();
NavigationManager.NavigateTo("listbooks");
}
Task SendMessage() => hubConnection.SendAsync("SendMessage");
public bool IsConnected =>
hubConnection.State == HubConnectionState.Connected;
public void Dispose()
{
_ = hubConnection.DisposeAsync();
}
void Cancel()
{
NavigationManager.NavigateTo("listbooks");
}
}

Here also, we have initialized the hub connection inside the “OnInitializedAsync” method. If you look at the “CreateBook” method, you can notice that we have sent a notification to hub server after saving the data to database using API post method. Currently, we are not sending any additional message with this notification. We can send any object along with hub notification, but in our application, we have not implemented that. Whenever, we send a push notification from here, all the other open clients will receive the push notification and inside the ListBooks component. We have already implemented the code to load the data from database after receiving new notification.
We can create a new razor component “EditBook” and add below code inside the component file.
EditBook.razor
@page "/editbook/{id}"
@using Microsoft.AspNetCore.SignalR.Client
@using BlazorSignalR.Shared
@inject HttpClient Http
@inject NavigationManager NavigationManager
<h2>Edit Book</h2>
<hr />
<form>
<div class="row">
<div class="col-md-8">
<div class="form-group">
<label for="Name" class="control-label">Name</label>
<input id="Name" class="form-control" @bind="@book.Name" />
</div>
<div class="form-group">
<label for="Department" class="control-label">ISBN</label>
<input id="Department" class="form-control" @bind="@book.Isbn" />
</div>
<div class="form-group">
<label for="Designation" class="control-label">Author</label>
<input id="Designation" class="form-control" @bind="@book.Author" />
</div>
<div class="form-group">
<label for="Company" class="control-label">Price</label>
<input id="Company" class="form-control" @bind="@book.Price" />
</div>
</div>
</div>
<div class="row">
<div class="form-group">
<input type="button" class="btn btn-primary" @onclick="@UpdateBook" value="Update" />
<input type="button" class="btn" @onclick="@Cancel" value="Cancel" />
</div>
</div>
</form>
@code {
private HubConnection hubConnection;
[Parameter]
public string id { get; set; }
Book book = new Book();
protected override async Task OnInitializedAsync()
{
book = await Http.GetFromJsonAsync<Book>("api/books/" + id);
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/broadcastHub"))
.Build();
await hubConnection.StartAsync();
}
protected async Task UpdateBook()
{
await Http.PutAsJsonAsync("api/books/" + id, book);
if (IsConnected) await SendMessage();
NavigationManager.NavigateTo("listbooks");
}
Task SendMessage() => hubConnection.SendAsync("SendMessage");
public bool IsConnected =>
hubConnection.State == HubConnectionState.Connected;
public void Dispose()
{
_ = hubConnection.DisposeAsync();
}
void Cancel()
{
NavigationManager.NavigateTo("listbooks");
}
}
Literally, we have used the same approach here also like AddBook component to send a push notification. After updating the data, hub will send a notification to all open clients and client will immediately refresh the data (in ListBooks component) after receiving the notification.
We can create a DeleteBook component and use below code.
DeleteBook.razor
@page "/deletebook/{id}"
@using Microsoft.AspNetCore.SignalR.Client
@using BlazorSignalR.Shared
@inject HttpClient Http
@inject NavigationManager NavigationManager
<h2>Delete</h2>
<p>Are you sure you want to delete this Book with Id: <b>@id</b></p>
<br />
<div class="col-md-4">
<table class="table">
<tr>
<td>Name</td>
<td>@book.Name</td>
</tr>
<tr>
<td>ISBN</td>
<td>@book.Isbn</td>
</tr>
<tr>
<td>Author</td>
<td>@book.Author</td>
</tr>
<tr>
<td>Price</td>
<td>@book.Price</td>
</tr>
</table>
<div class="form-group">
<input type="button" value="Delete" @onclick="@Delete" class="btn btn-primary" />
<input type="button" value="Cancel" @onclick="@Cancel" class="btn" />
</div>
</div>
@code {
[Parameter]
public string id { get; set; }
Book book = new Book();
private HubConnection hubConnection;
protected override async Task OnInitializedAsync()
{
book = await Http.GetFromJsonAsync<Book>("api/books/" + id);
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/broadcastHub"))
.Build();
await hubConnection.StartAsync();
}
Task SendMessage() => hubConnection.SendAsync("SendMessage");
public bool IsConnected =>
hubConnection.State == HubConnectionState.Connected;
public void Dispose()
{
_ = hubConnection.DisposeAsync();
}
protected async Task Delete()
{
await Http.DeleteAsync("api/books/" + id);
if (IsConnected) await SendMessage();
NavigationManager.NavigateTo("listbooks");
}
void Cancel()
{
NavigationManager.NavigateTo("listbooks");
}
}
We have sent the push notification after deleting the data from database using API delete method.
We can add the navigation menu for Books data inside the “NavMenu” shared component.
NavMenu.razor
<div class="top-row pl-4 navbar navbar-dark">
<a class="navbar-brand" href="">BlazorSignalR</a>
<button class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="listbooks">
<span class="oi oi-list-rich" aria-hidden="true"></span> Book Details
</NavLink>
</li>
</ul>
</div>
@code {
private bool collapseNavMenu = true;
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}
We have completed the entire coding part. We can run the application.
Click the “Books Details” navigation button and add a new book detail. You can open the same application inside a new browser or tab in the same time and notice that the browser will be automatically refreshed after saving the data in current browser.

We can edit or delete the book data as well.

Conclusion
In this post, we have seen how to create a Real-time web application with Blazor WebAssembly and SignalR. We have created a simple Book data application with all CRUD operations and see that how the data is updated (automatically refreshed) in different browsers in real-time. We can see more exciting features of Blazor WebAssembly in upcoming articles. Please feel free to share your valuable feedback.

awais awanPosted Aug 2, 2024, 10:52 AM
Who to send user to user message only user can view there own message not other one user. Thanks
a mPosted Jun 18, 2021, 6:14 AM
Hi , when i change data from sql server this project cant show changes real time. because sqldependency is reqired. how to implement this problem in your project .thanks
mars sidPosted Sep 23, 2020, 8:19 AM
While creating project I am getting just blazor app template and on creation it is showing just one project in the solution not three
armando paredesPosted Aug 4, 2020, 10:47 AM
Como puedo ahcer para que ene le mensaje tomo el Identificador que es esta modificando agregando
S PPosted Aug 2, 2020, 5:17 PM
Hello, I am getting the following error when I followed this example.
S PPosted Aug 2, 2020, 5:17 PM
Crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] Unhandled exception rendering component: Property CookieContainer is not supported.System.PlatformNotSupportedException: Property CookieContainer is not supported. at System.Net.Http.HttpClientHandler.set_CookieContainer (System.Net.CookieContainer value) <0x2fdce78 + 0x0000c> in <filename unknown>:0 at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.CreateHttpClient () <0x2fcd830 + 0x0007e> in <filename unknown>:0 at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection..ctor (Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionOptions httpConnectionOptions, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) <0x2fc7488 + 0x000c0> in <filename unknown>:0 at Microsoft.AspNetCore.SignalR.Client.HttpConnectionFactory.ConnectAsync (Microsoft.AspNetCore.Connections.TransferFormat transferFormat, System.Threading.CancellationToken cancellationToken) <0x2fc5fe8 + 0x0003a> in <filename unknown>:0 at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsyncCore (System.Threading.CancellationToken cancellationToken) <0x2fc0508 + 0x001ea> in <filename unknown>:0 at System.Threading.Tasks.ForceAsyncAwaiter.GetResult () <0x2ff4a28 + 0x00026> in <filename unknown>:0 at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsync (System.Threading.CancellationToken cancellationToken) <0x2fb7070 + 0x0010e> in <filename unknown>:0 at BlazorSignalRApp.Client.Pages.ListBooks.OnInitializedAsync () [0x00092] in C:\Workspace\stuff\BlazorSignalRApp\BlazorSignalRApp\Client\Pages\ListBooks.razor:64 at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync () <0x2c8e7d0 + 0x0013a> in <filename unknown>:0 at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask (System.Threading.Tasks.Task taskToHandle) <0x2ede068 + 0x000b6> in <filename unknown>:0 The thread 0x5c04 has exited with code 0 (0x0).
armando paredesPosted Jul 31, 2020, 2:41 PM
Hola como estas una consulta al generar este codigo me da error en private HubConnectionHandler y no m aparece @using Microsoft.AspNetCore.SignalR.Client
Upendra Pratap ShahiPosted Jun 8, 2020, 3:04 PM
Nice one article and thank you for sharing the same.
Johnson HPosted Jun 7, 2020, 3:52 PM
Thanks a lot for your another useful article. Normally I use SignalR in my ASP.NET MVC + Angular project, but it seems to not works properly due to some notification blocking by Chrome, etc. On the other hand, I am wondering the following issues: 1) What is the difference between using SignalR and SignalR + Blazor? 2) Can I also apply this (Blazor) to my project based on ASP.NET MVC? Could you pls clarify me about these 2 points?
Sundaram SubramanianPosted Jun 5, 2020, 5:39 AM
Congrats for your 100 th Article