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

Here I gave one example how my Startup.cs looks like, but with Asp.NET Core 3.0 Microsoft change it, you can read about it here

Here is my new Startup.cs:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Radius
{
  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.AddControllers();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
      if (env.IsDevelopment())
      {
        app.UseDeveloperExceptionPage();
      }

      app.UseHttpsRedirection();
      app.UseDefaultFiles();
      app.UseStaticFiles();
      app.UseRouting();
      app.UseCors();

      app.UseEndpoints(endpoints =>
      {
        endpoints.MapControllers();
      });
    }
  }
}

Here I gave one example of jQuery Ajax POST method, but problem with that example is that you don't know if returned JSON is wrong. Now I will give example when .NET side returns wrong JSON, and error handling in jQuery.

index.html remains same:

<!DOCTYPE html>
<head>
	<meta charset="utf-8" />
	<script defer type="text/javascript" src="lib/jquery-3.5.1.js"></script>
	<script defer type="text/javascript" src="js/index.js"></script>
</head>

<body>
	<button id="button" type="button">Click Me!</button>
</body>
index.js:
$( "#button" ).click(function() {
    var json = JSON.stringify({"cities": ["Aurel Vlaicu", "Asquins", "Arnoldstein"]});

    $.ajax({
        url: "api/Empty/",
        type: "POST",
        data: json,
        dataType: "json",
        contentType: "application/json; charset=utf-8",
        success: function (data) {
            alert("Success.");
        }
    }).done(function (data) {
        try {
            json = $.parseJSON( JSON.stringify(data));
        } catch (e) {
            alert("Error not JSON. " + e.message)
        }
    }).error(function (xhr, ajaxOptions, thrownError) {
        alert("Error.");
    }).fail(function (data) {
        alert("Sorry. Server unavailable. ");
    }); 
});
.NET side:
    [HttpPost]
    public ActionResult Get([FromBody] Filter filter)
    {
      string returnValue = string.Empty;

      foreach (var city in filter.cities)
      {
        returnValue = $"{city}, {returnValue}";
      }

      return Ok(JsonSerializer.Serialize(returnValue));
    }
Notice that in "$.ajax.done" I added try..catch block, and JSON.stringify because of dataType: "json" jQuery will automatically parse string into JSON.
Here I have explained how to create empty Asp.Net Core application, now using this article here is one example on how to use jQuery Ajax call to send JSON to Asp.Net core API.

index.html:

<!DOCTYPE html>
<head>
	<meta charset="utf-8" />
	<script defer type="text/javascript" src="lib/jquery-3.5.1.js"></script>
	<script defer type="text/javascript" src="js/index.js"></script>
</head>

<html xmlns="http://www.w3.org/1999/xhtml">
	 <button id="button" type="button">Click Me!</button> 
</html>
index.js:
$( "#button" ).click(function() {
    var json = JSON.stringify({"cities": ["Aurel Vlaicu", "Asquins", "Arnoldstein"]});

    $.ajax({
        url: "api/Empty/",
        type: "POST",
        data: json,
        contentType: "application/json; charset=utf-8"
    }).done(function (data) {
        alert("Success.");
    }).fail(function (data) {
        alert("Sorry. Server unavailable. ");
    }); 
});
Asp.Net core API:
using Microsoft.AspNetCore.Mvc;

namespace AspDotNetCorePostExample.Controllers
{
  [Route("api/[controller]")]
  [ApiController]
  public class EmptyController : ControllerBase
  {
    [HttpGet]
    public string Get()
    {
      return "Hello world";
    }

    [HttpPost]
    public string Get([FromBody] Filter filter)
    {
      string returnValue = string.Empty;

      foreach (var city in filter.cities)
      {
        returnValue = $"{city}, {returnValue}";
      }

      return returnValue;
    }

    public class Filter
    {
      public string[] cities { get; set; }
    }

  }
}
Notice in jQuery that in Ajax call I didn't add dataType: "json" since my API returns string and not JSON. In API notice [FromBody] in method signature, and Filter class which is defined exactly the same as JSON var json = JSON.stringify({"cities": ["Aurel Vlaicu", "Asquins", "Arnoldstein"]}); which I am sending to API.

Example download from here