In order to write this article, I was using this web site.

First I started new ASP.NET Core Empty project with Target Framework 5.0:

Then using nuget I installed Microsoft.AspNetCore.SignalR.Common:

I have added new folder "Hubs", where I added new class ProgressHub which is inherited from Hub:

using System.Threading;
using Microsoft.AspNetCore.SignalR;

namespace SignalRProgressBar.Hubs
{
  public class ProgressHub : Hub
  {
    public string msg = "Initializing and Preparing...";
    public int count = 100;

    public void CallLongOperation()
    {
      for (int x = 0; x <= count; x++)
      {

        // delay the process to see things clearly
        Thread.Sleep(100);

        if (x == 20)
          msg = "Loading Application Settings...";

        else if (x == 40)
          msg = "Applying Application Settings...";

        else if (x == 60)
          msg = "Loading User Settings...";

        else if (x == 80)
          msg = "Applying User Settings...";

        else if (x == 100)
          msg = "Process Completed!...";

        string myMessage = string.Format(msg + " {0}% of {1}%", x, count);
        Clients.All.SendAsync("ReceiveMessage", myMessage);
      }
    }
  }
}
In Startup.cs in the method ConfigureServices I added SignalR:
public void ConfigureServices(IServiceCollection services)
{
  services.AddSignalR();
}
In EndPoints I added Hub:
app.UseEndpoints(endpoints =>
{
	endpoints.MapHub<ProgressHub>("/progressHub");
});
Then I added wwwroot folder, and support for static files as I already explained here

After that I added index.html page in wwwroot folder, and I added Signalr JavaScript using client library:

As a search I wrote signalR and I have choosen aspnet-signalr:

I have added js library to html

<script defer src="aspnet-signalr/signalr.min.js"></script>
In folder js I have added my Javascript file, which look like this:
var connection = new signalR.HubConnectionBuilder().withUrl("../progressHub").build();
connection.start().then(function () {
    connection.invoke("CallLongOperation").catch(function (err) {
        return console.error(err.toString());
    });
});

connection.on("ReceiveMessage", function (message) {
    document.getElementById('progressbar').value = document.getElementById('progressbar').value + 1; 
    document.getElementById('progressbarText').innerText = message; 
});

My html looks like this:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
    <script defer src="aspnet-signalr/signalr.min.js"></script>
    <script defer src="js/progress.js"></script>
</head>
<body>
    <div style="width: 30%; margin: 0 auto;">

        <label id="progressbarText" style="font-family: Tahoma; font-size: 0.9em; color: darkgray; margin-top: 230px; padding-bottom: 5px; display:inline-block" for="progressbar">
            Initializing and Preparing...
        </label>
        <br />
        <progress id="progressbar" value="1" max="100"></progress>
    </div>
</body>
</html>
Notice line in controller:
Clients.All.SendAsync("ReceiveMessage", myMessage);
and line in Javascript:
connection.on("ReceiveMessage", function (message) {
    document.getElementById('progressbar').value = document.getElementById('progressbar').value + 1; 
    document.getElementById('progressbarText').innerText = message; 
});
This is how SignalR communicates with client and server using WebSocket. Method name on both sides has to be the same.

Example download from here