Real-Time Progress Updates with ASP.NET Core and SignalR
In this article, we'll walk through building a simple web application to demonstrate real-time progress updates for a long-running server task using ASP.NET Core and SignalR. This is a common requirement in web applications where you need to provide feedback to the user for operations like file uploads, data processing, or report generation.
Prerequisites
Before you begin, ensure you have the following installed:
- .NET 9 SDK (or your preferred version)
- A code editor like Visual Studio Code
- Basic understanding of C#, ASP.NET Core, and JavaScript.
Project Setup
First, let's create a new ASP.NET Core Web API project. Open your terminal and run the following commands:
dotnet new webapi -n SignalRProgressDemo
cd SignalRProgressDemo
Next, we need to add the necessary SignalR package.
dotnet add package Microsoft.Azure.SignalR
This will set up a basic project structure for us to build upon.
Project Structure
Here is the folder structure of our SignalRProgressDemo
project:
SignalRProgressDemo/
├── Controllers/
│ └── InsertController.cs
├── Properties/
│ └── launchSettings.json
├── wwwroot/
│ └── index.html
├── appsettings.Development.json
├── appsettings.json
├── Program.cs
├── ProgressHub.cs
├── SignalRProgressDemo.csproj
└── SignalRProgressDemo.sln
File-by-File Breakdown
Here’s a detailed look at each of the files we'll be creating or modifying.
1. Program.cs
This is the entry point of our application. We need to configure services for SignalR and controllers, and map the SignalR hub and controller endpoints.
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddSignalR();
//builder.Services.AddSignalR().AddAzureSignalR("<connection_string>");//builder.Services.AddSignalR().AddAzureSignalR(builder.Configuration["AzureSignalR:ConnectionString"]);
builder.Services.AddControllers();
builder.Services.AddOpenApi();
var app = builder.Build();
app.UseDefaultFiles();
app.UseStaticFiles();
app.MapHub<SignalRProgressDemo.ProgressHub>("/progressHub");
app.MapControllers();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast");
app.Run();
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
2. ProgressHub.cs
This is our SignalR hub. A hub is a class that serves as a high-level pipeline that handles client-server communication. It has methods that can be called by clients, and the server can also invoke methods on the clients.
Create a new file ProgressHub.cs
and add the following code:
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace SignalRProgressDemo
{
public class ProgressHub : Hub
{
// Method to send progress updates to clients
public async Task SendProgress(int percent)
{
await Clients.All.SendAsync("ProgressUpdate", percent);
}
}
}
3. Controllers/InsertController.cs
This API controller will simulate a long-running task. When the InsertTables
endpoint is called, it will loop and send progress updates to the clients via the ProgressHub
.
Create a Controllers
folder if it doesn't exist, and add a new file InsertController.cs
with the following code:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace SignalRProgressDemo.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class InsertController : ControllerBase
{
private readonly IHubContext<ProgressHub> _hubContext;
public InsertController(IHubContext<ProgressHub> hubContext)
{
_hubContext = hubContext;
}
[HttpPost]
public async Task<IActionResult> InsertTables()
{
int totalTables = 10;
for (int i = 1; i <= totalTables; i++)
{
// Simulate table insert (replace with actual DB logic)
await Task.Delay(1000); // Simulate time-consuming insert
int percent = (i * 100) / totalTables;
var message = $"Table {i} of {totalTables} inserted.";
await _hubContext.Clients.All.SendAsync("ProgressUpdate", percent, message);
}
await _hubContext.Clients.All.SendAsync("ProgressUpdate", 100, "All tables inserted successfully.");
return Ok(new { message = "All tables inserted." });
}
}
}
4. wwwroot/index.html
This is our client-side application. It's a simple HTML page with JavaScript to connect to the SignalR hub, display a progress bar, and provide a button to start the server-side task.
Create a wwwroot
folder in your project root, and inside it, create an index.html
file:
<!DOCTYPE html>
<html>
<head>
<title>SignalR Progress Demo</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/7.0.5/signalr.min.js"></script>
<style>
#progressBar {
width: 100%;
background-color: #eee;
}
#progress {
width: 0%;
height: 30px;
background-color: #4caf50;
text-align: center;
color: white;
}
</style>
</head>
<body>
<h2>Insert Progress</h2>
<div id="progressBar">
<div id="progress">0%</div>
</div>
<div id="message"></div>
<button onclick="startInsert()">Start Insert</button>
<script>
const connection = new signalR.HubConnectionBuilder()
.withUrl("/progressHub")
.build();
connection.on("ProgressUpdate", function (percent, message) {
const progress = document.getElementById("progress");
const messageDiv = document.getElementById("message");
progress.style.width = percent + "%";
progress.textContent = percent + "%";
messageDiv.textContent = message;
});
async function startInsert() {
await fetch("/api/insert", { method: "POST" });
}
connection.start().catch(function (err) {
return console.error(err.toString());
});
</script>
</body>
</html>
5. SignalRProgressDemo.csproj
This is the project file. It should include the package reference for SignalR.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.6" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.2.0" />
<PackageReference Include="Microsoft.Azure.SignalR" Version="1.31.0" />
</ItemGroup>
</Project>
6. appsettings.json
This file contains configuration settings for the application. For this demo, we can also include the Azure SignalR connection string if we were to deploy it.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"AzureSignalR": {
"ConnectionString": "Endpoint=https://rg2signalrr.service.signalr.net;AccessKey=;Version=1.0;"
}
}
7. Properties/launchSettings.json
This file configures how the application is launched for development.
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5186",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
How It Works
- Client Connection: When the
index.html
page is loaded, the JavaScript code establishes a connection to ourProgressHub
on the server. - Initiating the Task: The user clicks the "Start Insert" button, which sends a
POST
request to the/api/insert
endpoint. - Server-Side Processing: The
InsertController
receives the request and starts a simulated long-running task. - Broadcasting Progress: Inside the loop, the controller calculates the progress percentage and uses the
IHubContext<ProgressHub>
to send a "ProgressUpdate" message to all connected clients. - Client-Side Update: The JavaScript on the client side is listening for the "ProgressUpdate" message. When it receives one, it updates the width and text of the progress bar.
Running the Project
To run the project, open your terminal in the project's root directory and execute:
dotnet run
Then, open your web browser and navigate to http://localhost:5186
(the port may vary, check the terminal output). You should see the "Insert Progress" page. Click the "Start Insert" button to see the progress bar update in real-time.
Conclusion
You've successfully built a web application that provides real-time progress updates using ASP.NET Core and SignalR. This pattern is incredibly useful for improving user experience in applications with long-running background tasks. From here, you could extend this project to handle real database operations, file processing, or any other asynchronous task.
No comments:
Post a Comment